Appunti sulla realizzazione di questo sito. Il guestbook

Il guestbook di questo sito è realizzato con due semplici script PHP. Uno si occupa di memorizzare i messaggi in un file di testo, l'altro di leggerli.

Lo script di lettura

La pagina web principale del mio guestbook contiene lo script di lettura e ha la seguente struttura

<h2>GuestBook</h2>

<!-- IL FORM PER L'INVIO DEI DATI ALLO SCRIPT DI
     SCRITTURA TRAMITE IL METODO "POST" -->

<!-- GESTIONI ERRORI CHE ARRIVANO DALLO SCRIPT
     DI SCRITTURA TRAMITE IL METODO "GET" -->
	
<!-- LETTURA E STAMPA DEL "DATABASE" DEL GUESTBOOK -->

Vediamo prima di tutto il form per l'invio del messaggio

<form action="./send-message.php" method="post">
<p>
Nome (max 24 caratteri):<br/>
<input type="text" name="nome" size="24" maxlength="24"/><br/><br/>
Messaggio (max 160 caratteri):<br/>
<textarea name="messaggio" maxlength="160" rows="4" cols="40" id="messaggio"></textarea><br/><br/>
<input type="submit" value="Invia"/>
</p>
</form>
<br/>

Come si vede è un semplice form con due campi. Impostare il numero massimo dei caratteri per il nome è semplice, lo permette la casella di testo stessa. Invece per quanto riguarda la textarea non ho trovato opzioni analoghe e ho dovuto gestire il numero massimo di caratteri con il php.

Vediamo ora la gestione degli errori (che vengono passati alla pagina principale del guestbook con il metodo GET). Il codice non ha bisogno di spiegazioni particolari.

<?
$error=$_GET['error'];

if (!empty($error)) {
  switch ($error) {
    case 1:
        echo "<p><b>NON PUOI INSERIRE DUE MESSAGGI CONSECUTIVAMENTE!</b></p>\n";
        break;
    case 2:
        echo "<p><b>HAI DIMENTICATO DI INSERIRE IL NOME O IL MESSAGGIO!</b></p>\n";
        break;
    case 3:
        echo "<p><b>HAI SUPERATO LA LUNGHEZZA MASSIMA CONSENTITA PER IL MESSAGGIO (160 caratteri)!</b></p>\n";
        break;
  	case 4:
        echo "<p><b>HO COME L'IMPRESSIONE CHE TU STIA INSERENDO DELLO SPAM!</b></p>\n";
        break;
  }
}
?>

Prima di vedere il codice per stampare il contenuto del database del guestbook, vediamo come è fatto il database. Il database dove vengono memorizzati i vari messaggi del guestbook è un semplice file di testo con la seguente struttura:

DD Month YYYY  HH:MM"nome"messaggio"IP

Come si vede i campi sono separati dal carattere "

Ora che abbiamo visto come è strutturato il database, possiamo vedere lo script per leggerlo e stampare il codice HTML (una table dove ogni riga è un messaggio):

<?
$messaggi=file("./guestbook.txt");
$n=count($messaggi);
echo "<table id=\"guestbook\">\n <col id=\"fst\"/><col id=\"snd\"/>\n";
for($i=0; $i<$n; $i++) {
  // Reading the lines backward
  $r=$n-($i+1);
  list($data, $utente, $messaggio, $IP) = split('"', $messaggi[$r]);
  echo "<tr><td>" . $data . "<br/>" . "<b>".$utente."</b>" . "</td><td>" . $messaggio . "</td></tr>\n";
};
echo "</table>\n";
?>

Lo script di scrittura

Come è noto quando si clicca il pulsante di un form come quello mostrato sopra, viene richiamata la pagina indicata nel codice, nel nostro caso send-message.php. Il codice di questa nuova pagina potrà usare i dati inseriti nel form perché questi sono contenuti nell'array $_POST.

La pagina send-message.php ha la seguente struttura

<!-- definizione variabili -->

<!-- pulizia del contenuto dei campi del form -->

<!-- controllo errori e caricamento della pagina
    principale del guestbook passando il codice di errore
	con il metodo GET -->

<!-- scrittura messaggio nel database-->

Vediamo la definizione e assegnazione di alcune variabili iniziali

setlocale(LC_TIME, "it_IT");
$error = 0;
$db="./guestbook.txt";
$ip=$_SERVER['REMOTE_ADDR'];
// Read the IP of the last message
$file_ip=fopen("./last_IP","r");
$last_ip=fgets($file_ip);
fclose($file_ip);

L'unica cosa da spiegare è la presenza del file last_IP dove si trova l'indirizzo IP dell'ultimo messaggio scritto nel guestbook. Questo IP verrà confrontato con quello del messaggio attuale per vedere se è lo stesso o meno. Se è lo stesso, il messaggio non verrà scritto.

Il passo successivo è recuperare il testo del messaggio e il nome dell'autore (che sono stati inviati tramite il metodo POST) e fare un escape dei comandi HTML e PHP eventualmente presenti.

// Escaping of HTML and PHP commands
$messaggio=str_replace(array("\n","\r"),"",nl2br(htmlspecialchars($_POST['messaggio'],ENT_COMPAT,'UTF-8')));
$nome=str_replace(array("\n","\r"),"",nl2br(htmlspecialchars($_POST['nome'],ENT_COMPAT,'UTF-8')));

Tolgo i caratteri "pericolosi", poiché interpretabili, usando la funzione htmlspecialchars(). L'opzione ENT_COMPAT serve a indicare che verranno trasformati solo i caratteri ", &, < e > che diverranno rispettivamente &quot;, &amp;, &lt; e &gt;. UTF-8 è ovviamente la codifica da usare.

Come si vede, la stringa così ripulita, viene passata direttamente alla funzione nl2br che aggiunge il tag <br />; prima di ogni ritorno a capo (\n\r, \r\n, \n, \r). Infine la stringa risultante viene passata alla funzione str_replace che rimpiazza una sottostringa con un'altra. Nel nostro caso viene usata per eliminare i ritorni a capo, cioè le sequenze \n e \r. Questa operazione di sostituzione della forma degli a capo da php (\n, \r, ecc...) a HTML (<br />;) ci serve per poter fare in modo che nel database del guestbook, che è un semplice file di testo, ogni messaggio occupi una e una sola riga. Se non avessimo tolto gli a capo, il testo nel database avrebbe occupato più di una riga.

Poi c'è la gestione degli errori che si commenta da sé.

// Two consecutive message from the same IP are not allowed
if ($last_ip == $_SERVER['REMOTE_ADDR']) {
  $error=1;
  header ("Location: http://biccari.altervista.org/c/guestbook/?error=1");
  exit;
};

// Empty message or empty name is not allowed
if (empty($messaggio) or empty($nome)) {
  $error = 2;
  header ("Location: http://biccari.altervista.org/c/guestbook/?error=2");
  exit;
};

// message cannot be longer than 160 chars
if (strlen($messaggio)>160) {
  $error = 3;
  header ("Location: http://biccari.altervista.org/c/guestbook/?error=3");
  exit;
};

// Inserting the http string is not allowed
if (stripos($messaggio, 'http') !== false or strpos($nome, 'http') !== false) {
  $error = 4;
  header ("Location: http://biccari.altervista.org/c/guestbook/?error=4");
  exit;
};

Infine, se non ci sono stati errori, scrivo il nuovo messaggio nel database guestbook.txt e l'IP dell'autore nel file last_IP.

if ($error == 0) {
	$file=fopen($db,"a+");
	$stringa=ucwords(strftime("%d %B %Y  %H:%M")) . '"' . $nome . '"' . $messaggio . '"' . $_SERVER['REMOTE_ADDR'] . "\n";
	fwrite($file,$stringa);
	fclose($file);
	// Write the IP of the message
	$file_ip=fopen("./last_IP","w");
	fwrite($file_ip,$_SERVER['REMOTE_ADDR']);
	fclose($file_ip);
	header ("Location: http://biccari.altervista.org/c/guestbook");
	exit;
};

Da notare che i campi nel file di testo (data, autore, messaggio, IP) sono separati con un carattere non inseribile da un visitatore, cioè i doppi apici. In realtà un autore può inserire i doppi apici nel suo messaggio ma questi verranno trasformati, come visto sopra, nella sequenza di caratteri &quot;.