Home / Indice sezione
 www.icosaedro.it 

 Organizzare siti WEB per funzioni in PHP - Soluzione generale

Ultimo aggiornamento: 2008-01-21

Nell'articolo precedente abbiamo proposto un modello di programmazione WEB per funzioni usando solo le ancore HTML. Qui esaminiamo il problema della gestione dei FORM e dei bottoni. Inoltre perfezioneremo ulteriormente il nostro codice in modo che non sia più necessario eseguire la validazione dei parametri delle funzioni.

Indice

Implementare i FORM e i bottoni
I parametri dell'URL non sono affidabili
Proteggere l'integrità dei parametri dell'URL
Meccanismo di chiamata delle funzioni rivisto
Application Programming Interface (API)
Implementazione
Conclusioni
Limitazioni per Apache WEB server
Riferimenti

Implementare i FORM e i bottoni

Vogliamo poter assegnare una funzione target per ogni bottone. Questo è coerente con l'impostazione generale secondo la quale ogni volta che l'utente fa un click su di un'ancora o su di un bottone viene chiamata una funzione specifica del nostro programma, eventualmente con dei parametri appositamente predisposti.

Per fissare le idee, consideriamo ad esempio la classica rubrica telefonica, dove il record pk=1234 corrisponde al signor "Mario Rossi". La maschera qui sotto mostra i dati del record e permette di salvare le modifiche (bottone Salva) oppure cancellare il record (bottone Rimuovi):


Nome:
Cognome:
Telefono:
  


Il bottone Salva sarà associato alla funzione SalvaRecord(1234), che acquisisce il POST e salva i dati aggiornati nel record pk=1234. Invece il bottone Rimuovi sarà associato alla funzione RimuoviRecord(1234), che rimuove il record.

I parametri dell'URL non sono affidabili

Abbiamo visto nel precedente articolo che la funzione Anchor() permette di definire un'ancora, impostare la funzione associata, e definire anche dei parametri. Ad esempio:

Anchor("Rimuovi", "RimuoviRecord", "pk", "1234");

produce un'ancora che, se cliccata, causa la chiamata della funzione RimuoviRecord() con parametro di query HTML pk=1234. L'intenzione è che il parametro pk sia la chiave primaria del record da cancellare dal DB. Tuttavia non possiamo essere certi che l'utente ritorni un parametro corretto. Infatti l'utente potrebbe alterare deliberatamente o accidentamente l'URL, causando effetti imprevisti. Basterebbe immettere nella barra degli indirizzi del browser un URL confezionato così:

http://www.nostrosito.it/programma.php?fun=RimuoviRecord&pk=567

per causare la cancellazione del record di chiave primaria 567. Probabilmente questo fatto non sarebbe molto rilevante nella nostra banale applicazione dell'agenda telefonica personale, ma sarebbe potenzialmente disastroso in applicazioni più articolate esposte al pubblico o a personale non fidato.

Ma non solo i parametri possono essere artefatti, possono anche mancare del tutto! Cosa succederebbe se la funzione RimuoviRecord() procedesse ciecamente ad eseguire la cancellazione di un record di chiave primaria vuota? Probabilmente avremmo l'emissione di un errore per istruzione SQL non ben formata.

Ancora più grave sarebbe il caso in cui un utente malizioso, scoperta la breccia nella sicurezza del nostro programma, cominciasse a sperimentare l'inclusione di stringhe opportunamente confezionate per il parametro pk in modo da indurre il DB ad eseguire istruzioni arbitrarie.

Nella precedente implementazione dell'esempio 1 l'istruzione switch assicurava che almeno il nome della funzione da chiamare risultasse tra quelli previsti, perché diversamente veniva chiamata d'uffico la Home() che presentava la home page. Nessun controllo però avveniva sugli altri eventuali parametri, e pertanto la funzione doveva eseguire il necessario controllo sanitario. Questa strategia è tediosa, inefficiente, e prona agli errori. Vediamo ora come risolvere la questione in modo più elegante.

Proteggere l'integrità dei parametri dell'URL

Per assicurare che i parametri degli URL che ritornano dal client non siano stati alterati, possiamo ricorrere a un Hashed Message Authentication Code (HMAC). Si tratta di un codice calcolato sulla base del messaggio da controllare e di una chiave segreta nota solo al server. L'URL che invieremo al client sarà quindi composto dai parametri veri e propri e dall'HMAC. Quando il client ci rispedirà l'URL, il server dovrà estrarre i parametri e l'HMAC, e quindi dovrà verificare che l'HMAC ritornato sia valido. In definitiva, l'HMAC è una sorta di sigillo di autenticità che viene applicato dal server e che può essere verificato solo dal server.

Tra i vari algoritmi che si possono utilizzare, ne scegliamo uno basato sulla funzione digest MD5 applicata in questo modo (vedi ad esempio [Schneier] par. 18.14):

HMAC = MD5( CHIAVE . MD5( CHIAVE . PARAMETRI ) )

ovvero: la chiave segreta viene concatenata ai parametri e ne viene calcolato il digest MD5; il risultato viene concatenato di nuovo con la chiave e ne viene calcolato il digest; il digest risultante è l'HMAC che useremo.

Ottenuto l'HMAC, inseriamo nell'URL i parametri voluti e l'HMAC calcolato. Se il client ci ritornerà questo URL, dovremo estrarre i parametri, ricalcolarne l'HMAC e confrontarlo con l'HMAC ritornato. Se i due HMAC, cioè quello ritornato del client e quello calcolato di nuovo, coincidono, allora avremo la certezza quasi assoluta che i parametri ritornati dal client sono proprio quelli generati dal nostro programma nel momento in cui la pagina WEB era stata prodotta. Se invece i due HMAC non corrispondono, allora i parametri non sono validi e avremo rilevato una condizione anomala.

Meccanismo di chiamata delle funzioni rivisto

Soluzione per le ancore. I nostri URL conterranno 3 parametri: il primo, di nome fun contiene il nome della funzione; il secondo, di nome args contiene l'array degli argomenti della funzione; il terzo, di nome mac, contiene l'HMAC calcolato sulla base dei primi due e della chiave segreta.

Siccome gli argomenti di una funzione possono essere oggetti di tipo arbitrario, useremo la funzione serialize() per convertirli in una stringa di byte, e la funzione urlencode() per inserire il tutto nell'URL. Siccome i dati serializzati sono di tipo binario, eseguo anche una codifica base64_encode() prima di eseguire l'urlencode in modo da ridurre un po' la lunghezza degli URL prodotti. Ad esempio, la chiamata alla funzione

Anchor("Rimuovi", "RimuoviRecord", 1234);

produce questo codice HTML (scomposto in due righe per esigenze di formattazione):

<a href="/php/esempio2.cgi?fun=RimuoviRecord&args=YToxOntpOjA7aToxMjM0O30%3D
&mac=67ff4ea38308dc09dfae80c19f23e1f3">Rimuovi</a>

Possiamo togliere lo switch sui nomi delle funzioni, che ci costringeva a elencarle tutte. Ci possiamo risparmiare questa seccatura usando la funzione call_func_array() del PHP. Questa funzione richiede due argomenti: il nome della funzione da chiamare e un array degli argomenti.

In definitiva, se l'utente cliccherà su questa ancora, verrà chiamata automaticamente la funzione RimuoviRecord(1234).

Soluzione per i FORM. Per i FORM la cosa si complica un pochino, ma non più di tanto. Per prima cosa osserviamo che ad un bottone si possono assegnare due proprietà: il nome con l'attributo name, e il valore con l'attributo value. Ad esempio il codice HTML seguente

<INPUT type=submit name=bottoneOk value="OK">

produce un bottone "OK". Se si preme questo bottone, il client ritorna al server il parametro bottoneOk=OK, cioè ritorna il nome del bottone e il suo valore. Oltre al bottone premuto, il client ritorna al server anche tutti i campi visibili e quelli nascosti.

Se vogliamo differenziare il comportamento del programma a seconda del bottone premuto, dobbiamo basarci o sul nome del bottone o sul suo valore. Il valore del bottone non è adatto, sia perché il programma potrebbe subire la localizzazione in diverse lingue, sia perchè in una pagina ci potrebbero essere diversi bottoni con la stessa dicitura riportata su di essi. Dobbiamo quindi basarci sul nome. Decidiamo allora di numerare i bottoni progressivamente, e di chiamarli button1, button2, button3, e così via. Per ogni bottone aggiungiamo anche dei campi nascosti (hidden) che conservano i valori del nome della funzione, degli argomenti e dell'HMAC di controllo. Ad esempio, la chiamata alla funzione Button() come questa

Button("Rimuovi", "RimuoviRecord", 1234);

produrrà nella pagina HTML un codice simile a questo (anche qui scomposto in più righe per maggiore chiarezza):

<INPUT type=hidden name=fun1 value=RimuoviRecord>
<INPUT type=hidden name=args1 value="YToxOntpOjA7aToxMjM0O30=">
<INPUT type=hidden name=mac1 value=a2534ea1548ed3624644accea00c256c>
<INPUT type=submit name=button1 value="Rimuovi">

Il parametro hidden fun1 contiene il nome della funzione da chiamare se il bottone viene premuto; args1 è l'array (serializzato ed opportunamente codificato con base64_encode()) degli argomenti della funzione; mac1 è l'HMAC dei due campi precedenti. In definitiva, se l'utente cliccherà su questo bottone, verrà richiamata la funzione RimuoviRecord(1234).

Similmente per il secondo bottone avremo dei campi hidden di nome fun2, args2, mac2, oltre al bottone vero e proprio button2. In questo modo abbiamo risolto anche il problema di associare una funzione specifica ad ogni bottone.

In definitiva, per creare un FORM con due bottoni A e B che chiamano le funzioni a() e b("Mario Rossi", 456) basterà scrivere un codice simile a questo:

Form();
Button("A", "a");
Button("B", "b", "Mario Rossi", 456);
Form_();

Eventuali campi di input si possono inserire all'interno del FORM nel modo tradizionale.

Application Programming Interface (API)

Siamo quasi giunti alla fine del viaggio. Prima di vedere l'esempio completo, riassumiamo le funzioni che potremo usare per implementare le nostre pagine WEB dinamiche:

Anchor(string $testo, string $fun, ...)
Emette un'ancora che, qualora cliccata, causa la chiamata della funzione $fun con gli eventuali argomenti indicati.

Form()
Form_()

Rispettivamente, aprono e chiudono un form. Ad ogni chiamata alla prima funzione deve corrispondere una chiamata alla seconda. Le due chiamate non si possono incrociare. Quando si preme un bottone, il client invia al server solo i campi contenuti nel form cui appartiene il bottone.

Button(string $testo, string $fun, ...)
Emette un bottone che, qualora cliccato, causa la chiamata della funzione $fun con gli argomenti indicati. La funzione potrà acquisire i valori dei campi del form come di consueto attraverso l'array $_POST[]. Su questi ultimi si dovrà procedere alla validazione secondo quanto richiesto dall'applicazione specifica.

Osserviamo che si possono inserire ancore all'interno dei form, e che si possono inserire più form in una stessa pagina; i bottoni devono stare all'interno di un form.

Il codice dell'esempio riporta anche altre funzioni di utilità per la costruzione delle pagine WEB, come Testa($titolo) e Piede() che aprono e chiudono una pagina. Agendo su queste funzioni è possibile cambiare rapidamente l'aspetto del nostro sito.

Implementazione

Finalmente vediamo l'implementazione del tutto. Il codice questa volta è un pò lungo e non lo riporto in questa pagina. Lo puoi vedere cliccando qui sotto:

La logica di funzionamento è piuttosto semplice:

Conclusioni

Lo strumento che stiamo sviluppando comincia a prendere forma e potrebbe già risultare utile in alcune applicazioni pratiche. Lo sviluppo di applicazioni articolate si semplifica e il risultato è un codice pulito e facilmente manutenibile.

Rimangono aperti ancora alcuni problemi che esamineremo negli articoli seguenti.

Limitazioni per Apache WEB server

Esistono delle limitazioni alla lunghezze delle richieste GET: il server Apache dispone della direttiva LimitRequestLine il cui valore default è di 8190 bytes. Questo pone un limite al numero e alla lunghezza degli argomenti delle funzioni.

Riferimenti

[Schneier] B. Schneier, "Applied Criptography", Wiley, 1996.


Umberto Salsi
Commenti
Contatto
Mappa
Home / Indice sezione
An abstract of the latest comments from the visitors of this page follows. Please, use the Comments link above to read all the messages or to add your contribute.

2018-05-10 by Umberto Salsi <salsi@icosaedro.it>
Re: web-per-funzioni a quando gli articoli previsti?
Guest wrote: [...] Avevo del codice da parte, ma non mi decidevo a dagli una sistemata in modo da renderlo adatto alla pubblicazione. Adesso è disponibile tutto insieme alla libraria standard di PHPLint; il seguente tutorial http://www.icosaedro.it/phplint/web spiega tutte le funzionalità supportate, e cioè: - Gli sticky form che, con i relativi controlli, permettono di creare semplici form entro-contenuti, con eventi associabili a metodi. - Il "web per funzioni" o "bt_", che permette di collegare fra loro eventi delle pagine associandoli a funzioni del programma (normalmente metodi statici di classe). - I "bt form", che sono la logica integrazione tra i form e bt_ e permettono di realizzare applicazioni articolate. - Esempio di sito web realizzato con bt form, a cui bisogna aggiungere il "web commenting system" con il quale sto scrivendo questo stesso commento. Quindi abbiamo tutto: tutorial, codice open source ed esempi. Ciao, - Umberto Salsi[more...]

2010-08-12 by Guest
web-per-funzioni a quando gli articoli previsti?
Ho scoperto recentemente il tuo sito è meravigliosamente interessante. Vedendo la data degli articoli "web per funzioni" credo che tu non abbia più intenzione di proseguire sull'argomento. Sarebbe un peccato! Credo che molti utenti del tuo sito ti sarebbero grati se invece continuassi a condividere le tue ottime conoscenze. Qualunque cosa tu decida ...grazie per gli articoli presenti. Buon lavoro.[more...]

2008-09-16 by Umberto Salsi
Re: ottimo lavoro ma....
Anonymous wrote: [...] Il file della chiave per il calcolo dell'HMAC è un qualsiasi file non leggibile dai visitatori del sito che si può collocare fuori dalla DOCUMENTROOT del sito: define('MAC_KEY_FILE', '../../MAC_KEY'); In alternativa se non è disponibile una directory fuori dal DOCUMENTROOT (molti hosting economici ricadono in questo caso) si può sempre usare il solito trucco di definire un file PHP di nome "key.php" che contiene qualcosa come <?php # abcdefghij ?> dove il commento abcdefghij è una stringa arbitraria di caratteri casuali. La costante MAC_KEY_FILE diventa allora: define('MAC_KEY_FILE', 'key.php'); In questo modo i visitatori del sito, anche se tentano di visualizzare il contenuto del file key.php, otterranno una pagina bianca. [more...]

2008-09-15 by Guest
ottimo lavoro ma....
chi esmina il codice trovera una define di mac file che per chiarezza potrebbe essere aggiunto al codice per avere un esempio completo.[more...]

2008-08-26 by Guest
the next one?
dovrò aspettare molto per i prossimi articoli? :) davvero un ottimo lavoro [more...]