Cookie

27. Gli SharedObject

In questa lezione vedremo cosa sono gli SharedObject e come utilizzarli in locale come dei tradizionali cookie.

Gli SharedObject possono essere usati anche in remoto con tecnologie come Flash Media Server (ex Flash Communication Server) o Red5, ma non sarà argomento di questa guida.

Per sfruttare questo utile strumento ci occorrono solamente due classi :

  • SharedObject - Classe di base per memorizzare dati negli SharedObject.
  • SharedObjectFlushStatus - Collezione di costanti per il valore restituito dal metodo flush della classe SharedObject.

Per rendere la nostra applicazione più robusta, effettueremo anche dei controlli per verificare che l'utente non abbia disabilitato la memorizzazione dei dati locali, e che ci sia inoltre lo spazio su disco sufficiente per scrivere i nostri dati.

L'utente può decidere quanto spazio può avere Flash Player per memorizzare questi dati, cliccando di destro su un filmato Flash e scegliendo poi la voce "Impostazioni..." come mostrato in questa schermata.

Prima di introdurre il codice vero e proprio, passiamo velocemente in rassegna i metodi e le proprietà che ci interessano :

  • getLocal() - E' un metodo statico che ci consente di inizializzare un'istanza di SharedObject da utilizzare per leggere i dati.
    Se non viene trovato lo SharedObject specificato allora prova a crearne uno.
  • data - E' la proprietà attraverso cui ci è possibile memorizzare dei valori nel nostro SharedObject. Non è possibile assegnare dei valori direttamente alla proprietà data, ma essendo un oggetto dinamico è possibile aggiungervi delle nuove proprietà a cui assegnare poi i nostri dati.
  • flush() - Scrive i dati su disco restituendo dei valori per verificare se la scrittura è avvenuta con successo.

Il sorgente che segue è un'applicazione che tenta di creare uno SharedObject di nome "lastclick", nel momento in cui l'utente clicca sul pulsante "Cookie", memorizzando la data e l'ora in cui è avvenuto il click.
Chiudendo e riaprendo il filmato si potrà notare che effettivamente il Flash Player ha memorizzato il dato per renderlo disponibile anche dopo la chiusura dell'applicazione.

package
{
	import fl.controls.Button;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;

	import flash.events.MouseEvent;
	import flash.display.MovieClip;

	import flash.net.SharedObject;
	import flash.net.SharedObjectFlushStatus;

	public class HelloWorld extends MovieClip
	{
		private var button:Button;
		private var info:TextField;
		private var format:TextFormat;
		private var cookie:SharedObject;

		public function HelloWorld():void
		{
			this.cookie = SharedObject.getLocal("lastclick");
			this.format = new TextFormat("Verdana", 10, 0x000000);
			this.format.align = TextFormatAlign.CENTER;

			this.info = new TextField();
			this.info.x = 10;
			this.info.y = 10;
			this.info.width = 230;
			this.info.height = 16;
			this.info.border = true;
			this.info.defaultTextFormat = this.format;
			this.info.text = (this.cookie.size > 0) ? this.cookie.data.click : "Mai cliccato";

			this.button = new Button();
			this.button.label = "Cookie";
			this.button.width = 70;
			this.button.height = 20;
			this.button.move(10, 35);

			this.button.addEventListener(MouseEvent.CLICK, this.setSO);

			this.addChild(this.info);
			this.addChild(this.button);
		}

		private function setSO(evt:MouseEvent):void
		{
			var adesso:Date = new Date();
			this.cookie.data.click = new String(adesso.getDate());
			this.cookie.data.click += "/" + (adesso.getMonth() + 1);
			this.cookie.data.click += "/" + adesso.getFullYear();
			this.cookie.data.click += " - " + adesso.getHours();
			this.cookie.data.click += ":" + adesso.getMinutes();
			this.cookie.data.click += ":" + adesso.getSeconds();

			try
			{
				var flushed:String = new String(this.cookie.flush());

				switch (flushed)
				{
					case SharedObjectFlushStatus.PENDING :
						this.info.text = "Spazio insufficiente";
						break;
					case SharedObjectFlushStatus.FLUSHED :
						this.info.text = this.cookie.data.click;
						break;
					default : this.info.text = "Errore imprevisto";
				}
			}
			catch (err:Error)
			{ this.info.text = "SharedObject disabilitati"; }
		}
	}
}

Come si può notare dal codice, se il metodo flush fallisce nella scrittura solleva un'eccezione di tipo Error, mentre se lo spazio è insufficiente ritorna semplicemente il valore che corrisponde alla costante SharedObjectFlushStatus.PENDING.

Nota : per compilare con successo il codice soprastante, è necessario aggiungere il componente Button alla libreria del filmato Flash.

Pannello Impostazioni

L'esempio precedente è stato creato a scopo didattico, non rappresenta effettivamente una situazione di applicazione reale.
Considerando che la maggior parte dell'utenza non ha mai aperto il pannello Impostazioni del Flash Player e non ha idea di cosa sia uno SharedObject, sarà senzaltro più utile aprire automaticamente il pannello piuttosto che riportare il freddo messaggio "Spazio insufficiente".

Per procedere ci serviranno due nuove classi :

  • Security - Collezione di costanti e metodi statici.
  • SecurityPanel - Collezione di costanti.

Ci basterà utilizzare il metodo statico showSettings, il quale ci consentirà di aprire il pannello impostazioni desiderato specificando la rispettiva costante della classe SecurityPanel.

// ...
Security.showSettings(SecurityPanel.LOCAL_STORAGE);

42. Una classe per i Cookie

In questo capitolo extra, ripeterò l'esempio del capitolo precedente utilizzando la programmazione orientata agli oggetti (OOP).

Ho scritto una classe Cookie molto semplice ma che ci consente di trarre dei vantaggi che non avremmo con la sola funzione nativa setcookie.


Classe Cookie

Attributi :

  • name - Un nome per il cookie
  • value - Il valore da registrare nel cookie
  • expire - La durata del cookie espressa in secondi
  • path - Path del sito da cui è accessibile il cookie
  • domain - Dominio web da cui è accessibile il cookie
  • secure - Per impostare il cookie solo su connessioni sicure HTTPS
  • httponly - Per rendere accessibile il cookie solo attraverso il protocollo HTTP

Come per setcookie(), solo name è obbligatorio mentre tutti gli altri attributi sono facoltativi.

Metodi :

  • setcookie() - Metodo per impostare il cookie
  • getPath() - Metodo statico che restituisce la path dello script attualmente in esecuzione
  • unserialize() - Metodo statico per recuperare da un cookie l'istanza originale della classe Cookie
  • cancella() - Metodo statico per cancellare un cookie
  • __toString() - Metodo che restituisce direttamente il valore del cookie se questo viene usato come stringa

Listato della classe Cookie

Introduco ora il codice della classe Cookie che risiede in un file esterno nominato Cookie.php :

<?php

	class Cookie
	{
		public $name;
		public $value;
		public $expire;
		public $path;
		public $domain;
		public $secure;
		public $httponly;

		public function Cookie($n, $v = "", $e = 0, $p = NULL, $d = NULL, $s = false, $h = false)
		{
			$this->name = $n;
			$this->value = $v;
			$this->expire = ($e == 0) ? $e : (int) ($e * 60 * 60) + time();
			$this->path = ($p == NULL) ? Cookie::getPath() : $p;
			$this->domain = $d;
			$this->secure = $s;
			$this->httponly = $h;
		}

		public function setcookie($salva_tutto = false)
		{
			$cookie = ($salva_tutto) ? serialize($this) : $this->value;

			$_COOKIE[$this->name] = $cookie;
			setcookie($this->name, $cookie, $this->expire, $this->path,
			$this->domain, $this->secure, $this->httponly);
		}

		static public function getPath()
		{
			$cartelle = explode("/", $_SERVER["PHP_SELF"]);

			$path = "";
			$dimensione = count($cartelle) - 1;

			for ($i = 0; $i < $dimensione; $i  )
				$path .= $cartelle[$i] . "/";

			return $path;
		}

		static public function unserialize($nomeCookie)
		{
			$cookie = unserialize(stripslashes($_COOKIE[$nomeCookie]));

			if ($cookie instanceof Cookie)
				return $cookie;
			else
				throw new Exception("Cookie corrotto");
		}

		static public function cancella($nomeCookie)
		{
			unset($_COOKIE[$nomeCookie]);
			setcookie($nomeCookie, "", time() - 86400); // Imposta la scadenza al giorno prima
		}

		public function __toString()
		{
			return $this->value;
		}
	}

?>

Vantaggi della classe Cookie

Uno dei vantaggi di questa classe è quello di poter usare solo i parametri che ci interessano realmente, senza obbligatoriamente specificare anche tutti gli altri come avviene con setcookie().

Se ad esempio volessimo specificare solo il nome del cookie, un valore e impostare a true l'opzione httponly sarebbe sufficiente utilizzare il seguente codice :

<?php

	require_once("Cookie.php");
	
	$cookie = new Cookie("nome cookie", "valore del cookie");
	$cookie->httponly = true;
	$cookie->setcookie();

?>

Con setcookie() invece avremmo dovuto specificare per forza anche expire, path, domain e secure per poter arrivare ad httponly rendendo il codice molto meno intuitivo e leggibile. La variante che segue è equivalente al codice soprastante :

<?php

	setcookie("nome cookie", "valore del cookie", 0, "/", NULL, false, true);

?>

Altra particolarità di questa classe è che ci consente di specificare come scadenza direttamente il numero di ore a partire dal momento in cui viene impostato il cookie. Potete facilmente modificare la classe, ad esempio per considerare expire come il numero di giorni di vita del cookie e così via :

<?php

	$cookie = new Cookie("nome cookie", "valore del cookie", 24); // Dura 24 ore
	$cookie->httponly = true;
	$cookie->setcookie();

	// oppure

	setcookie("nome cookie", "valore del cookie", time()   (60 * 60 * 24), "/", NULL, false, true);

?>

Se un'istanza di Cookie viene stampata o concatenata come stringa, interviene il metodo magico __toString() restituendo direttamente il valore memorizzato nel cookie.

Punto di forza di questa classe è il metodo pubblico setcookie(), che come avrete notato prende un parametro $salva_tutto impostato di default al valore false.

Se richiamando la funzione lo impostate invece a true, il metodo setcookie() serializza l'oggetto e lo salva integro nel cookie.

Sarà poi possibile recuperare l'oggetto salvato con il metodo statico unserialize, permettendoci di leggere anche in un secondo momento tutte le proprietà del cookie, cosa impossibile se avessimo usato solo la funzione nativa setcookie().


Esempio classe Cookie

Di seguito riporto l'esempio del capitolo precedente riscritto per la classe Cookie con la serializzazione dell'oggetto ...

classi_cookie.php

<?php

	require_once("Cookie.php");

	if ($_POST["comando"] == "cancella")
	{
		Cookie::cancella("dato");
	}
	else if ($_POST["comando"] == "imposta")
	{
		$cookie = new Cookie("dato", $_POST["dato"], $_POST["expire"]);
		$cookie->setcookie(true);
	}

?>

<html>
	<head>
		<title>Impostiamo e cancelliamo i Cookie</title>
	</head>
	<body>

		<?php

			if (isset($_COOKIE["dato"]))
			{
				try
				{
					$cookie = Cookie::unserialize("dato");

					echo "Il Cookie \"$cookie->name\" ha il valore = \"$cookie->value\"";
					echo "<br />\nScade in data " . date("d/m/Y - H:i:s", $cookie->expire);
					echo "<br />\nE' accessibile dal dominio \"" . (($cookie->domain) ? $cookie->domain : "default") . "\"";
					echo " per la path \"$cookie->path\"";
					echo "<br />\nsecure = \"" . (($cookie->secure) ? "true" : "false") . "\"";
					echo "<br />\nhttponly = \"" . (($cookie->httponly) ? "true" : "false") . "\"";
				}
				catch (Exception $e)
				{
					echo "<h3>" . $e->getMessage() . "</h3>";
				}

				?>

				<br /><br />

				<form name="deletecookie" action="classi_cookie.php" method="post">
					<input type="hidden" name="comando" value="cancella" />

					<input type="submit" value="Cancella cookie" />
				</form>

				<?php
			}
			else
			{
				?>

				<form name="writecookie" action="classi_cookie.php" method="post">
					<table width="400" border="0" cellspacing="5" cellpadding="5">

						<tr>
							<td>Dato da memorizzare</td>
							<td><input type="text" name="dato" value="" /></td>

						</tr>
						<tr>
							<td>Scadenza del cookie</td>
							<td><input type="text" name="expire" value="" /></td>

						</tr>
						<tr>
							<td><input type="submit" value="Imposta cookie" /></td>

							<td> </td>
						</tr>
					</table>
					<input type="hidden" name="comando" value="imposta" />

				</form>

				<?php
			}
		
		?>

	</body>
</html>

Una volta impostato il cookie si ottiene questo output.


41. I Cookie

I cookie sono un metodo veloce ed efficace per "ricordarsi" di un utente.

Il sistema è molto semplice e permette di memorizzare dei dati nel browser del navigatore. Questi dati vengono scritti in un file che prende il nome di cookie, che sarà possibile creare se il browser dell'utente è configurato per accettarli.

Di norma i cookie sono attivi nella stragrande maggioranza dei browser, e molti siti richiedono che siano obbligatoriamente abilitati per poter usufruire dei servizi da loro offerti.

I cookie si possono impostare con due funzioni che PHP ci fornisce : setcookie() e setrawcookie().

In PHP i cookie fanno parte dell'intestazione HTTP, motivo per cui è necessario chiamare setcookie() prima di mandare qualsiasi dato in output al browser dell'utente.

Se non è possibile evitare degli output prima di settare il cookie, interviene in nostro aiuto la funzione ob_start(), che posticiperà automaticamente gli output dello script, bufferizzandoli finchè non saranno impostati i cookie o l'intestazione.

Una volta impostati i cookie, sarà possibile accedervi attraverso l'array globale $_COOKIE[].

Vediamo ora come è possibile impostare i cookie attraverso la funzione setcookie().

Prototipo della funzione

bool setcookie ( string name [, string value [, int expire [, string path [, string domain [, bool secure [, bool httponly]]]]]] )

Lista dei parametri di setcookie() :

  1. name - Obbligatorio - Definisce un nome per il cookie
  2. value - Facoltativo - Il valore da memorizzare nel cookie
  3. expire - Facoltativo - Il tempo di scadenza del cookie in formato timestamp Unix
  4. path - Facoltativo - La directory del server da cui sarà accessibile il cookie
  5. domain - Facoltativo - Il dominio web da cui sarà accessibile il cookie
  6. secure - Facoltativo - Per impostare il cookie solo su connessioni sicure HTTPS
  7. httponly - Facoltativo - Per rendere accessibile il cookie solo attraverso il protocollo HTTP
    Nota : httponly è disponibile da PHP 5.2.0 e non è supportato da tutti i browser ma è molto utile per ridurre gli attacchi di tipo XSS, impedendo l'accesso al cookie da linguaggi client-side come Javascript

Esempio Cookie

Vedremo ora un semplice esempio in una pagina singola, attraverso cui creeremo e cancelleremo un cookie servendoci di due form HTML separate in modo da poter inviare due comandi differenti.

Stavolta dividerò il codice in due pezzi per poterlo spiegare meglio ai neofiti.

test.php

Vediamo la prima parte della pagina, costituita da solo codice PHP e priva di output in modo da non inviare l'header della pagina prima di aver operato sul cookie.

Non uso volontariamente la funzione ob_start() che ci permette di bufferizzare l'output, per non diminuire inutilmente le prestazioni dello script, anche se in questo caso lo script è talmente leggero che non si noterebbe alcuna differenza, ma vi consiglio comunque di non usare mai ob_start() inutilmente.

<?php

	function cancellaCookie($nomeCookie)
	{
		unset($_COOKIE[$nomeCookie]);
		setcookie($nomeCookie, "", time() - 86400); // Imposta la scadenza al giorno prima
	}

	if ($_POST["comando"] == "cancella")
	{
		cancellaCookie("dato");
	}
	else if ($_POST["comando"] == "imposta")
	{
		$scadenza = (int) $_POST["expire"] + time();
		setcookie("dato", $_POST["dato"], $scadenza, "/test/");

		/* Scriviamo direttamente nell'array globale per non far fallire la prossima IF
		rendendo così il cookie disponibile da subito senza ricaricare la pagina */
		$_COOKIE["dato"] = $_POST["dato"];
	}

?>

In questa prima parte di codice ho creato la funzione cancellaCookie(), che per cancellare i cookie ne imposta la data di scadenza al giorno prima. Prima di chiamare setcookie() però effettua una chiamata ad unset().

Questo per evitare che si tenti di accedere al cookie cancellato prima che la pagina venga ricaricata.

I browser infatti cancellano il cookie (file) dall'hard disk se verificano che il cookie è effettivamente scaduto, ma appena cancellato rimane comunque disponibile nell'array globale $_COOKIE[] fino ad un nuovo caricamento pagina che forzerà l'aggiornamento dell'array.

Dopo la definizione della funzione cancellaCookie() troviamo un blocco IF - ELSE IF dove agiamo di conseguenza al comando inviato da una delle due form HTML che vedremo nel secondo pezzo della pagina.

Nella else if assegniamo la data di scadenza del cookie, sommando al valore $_POST["expire"] inviatoci da una form, la timestamp corrente restituita da time().


Infine ho richiamato setcookie() impostando "/test/" come path valida per il cookie, poichè la mia pagina test.php si trova al momento sotto il seguente URL : http://localhost/test/test.php.

Vediamo il secondo pezzo di test.php

<?php /* Incollate qui il primo pezzo di test.php per fare le prove */ ?>
<html>
	<head>
		<title>Impostiamo e cancelliamo i Cookie</title>
	</head>
	<body>


		<?php

			if (isset($_COOKIE["dato"]))
			{
				echo "Cookie impostato per ricordare il seguente dato = \"" . $_COOKIE["dato"] . "\"";

				?>

				<br /><br />

				<form name="deletecookie" action="test.php" method="post">
					<input type="hidden" name="comando" value="cancella" />

					<input type="submit" value="Cancella cookie" />
				</form>

				<?php
			}
			else
			{
				?>

				<form name="writecookie" action="test.php" method="post">
					<table width="400" border="0" cellspacing="5" cellpadding="5">

						<tr>
							<td>Dato da memorizzare</td>
							<td><input type="text" name="dato" value="" /></td>

						</tr>
						<tr>
							<td>Scadenza del cookie</td>
							<td><input type="text" name="expire" value="" /></td>

						</tr>
						<tr>
							<td><input type="submit" value="Imposta cookie" /></td>

							<td> </td>
						</tr>
					</table>
					<input type="hidden" name="comando" value="imposta" />

				</form>

				<?php
			}
		
		?>

	</body>
</html>

Questo secondo pezzo di codice è un misto di PHP e HTML, diviso sostanzialmente in due blocchi principali, una IF e la sua corrispondente ELSE.

Con la IF controllo se il cookie è impostato mediante un controllo con isset() sull'array globale $_COOKIE[], motivo per cui nel primo pezzo di codice settiamo il cookie sia manualmente che con la funzione setcookie().

Se il cookie è impostato viene stampato il suo valore e di seguito una form HTML per eliminarlo dal browser del client, che invierà una sola variabile di nome comando (name="comando") e impostata al valore "cancella" (value="cancella").

Questa variabile la controlliamo nel primo pezzo di codice attraverso la prima IF : if ($_POST["comando"] == "cancella")

Nell'ultima else abbiamo un'altra form HTML (writecookie) che ci servirà per impostare il cookie.

La form writecookie invia 3 valori :

  1. dato - Il valore da memorizzare nel cookie
  2. expire - La durata del cookie espressa in secondi
  3. comando - Assegnato a "imposta" per indicare allo script che deve impostare il cookie

La prima volta che si esegue la pagina test.php e tutte le volte in cui il cookie non è impostato o scaduto, si ottiene questo output sul browser.

Una volta impostato il cookie, riaprendo la pagina (non aggiornate con F5 o reinvierete i dati per POST) otterrete invece questo output.

Se poi state usando Firefox, che consiglio vivamente soprattutto se create siti in quanto ha una miriade di strumenti utili per noi sviluppatori, allora potete andare su "Strumenti" -> "Opzioni..." -> "Privacy" -> "Mostra i cookie..." e cercare il cookie appena impostato cercando per nome cookie o per dominio.

Una schermata del gestore cookie di Firefox con la lettura del cookie impostato nell'esempio soprastante.

Inoltre ricordatevi sempre di non memorizzare dati sensibili nei cookie, e se mettete delle password create un algoritmo di criptazione o appoggiatevi ad un algoritmo di terze parti che faccia un buon lavoro.

Aggiungo infine che se un browser rispetta gli standard dei cookie, non sarà possibile creare dei cookie più grandi di 4KB per un massimo di 20 cookie per dominio, e il browser dell'utente non accetterà inoltre dei cookie se ne ha già un totale di 200.

Per ovviare a questi problemi ci serviremo delle "Sessioni" che vedremo nei prossimi capitoli.

Nota : nella sezione Download potete trovare altre estensioni utili di Firefox per sviluppare applicazioni web.
Le consiglio tutte ma in particolare provate Firebug e Web Developer ... è tutto aggratis eh!

Condividi contenuti