Home / Indice sezione
 www.icosaedro.it 

 PHP File Download

Ultimo aggiornamento: 2018-08-03

In questo articolo esaminiamo il processo di download dei file via protocollo HTTP, cioè il trasferimento dal server al client. Vedremo in particolare come realizzare il download dei file, come gestire nomi di file contenenti caratteri diversi dall'ASCII, come gestire i problemi di compatibilità tra i vari browser, come gestire il caching e le sessioni. Gli esempi dati sono per il popolare linguaggio di programmazione PHP. Questo articolo completa l'articolo PHP File Upload (www.icosaedro.it/articoli/php-file-upload.html).

Indice

Come presentare i file all'utente
Come permettere il download all'utente
La ricetta per gli impazienti
Come codificare il nome del file
Gestire caratteri speciali ed estensioni
Come fare in pratica
La trappola delle sessioni
Caching
Il programma di test
Bibliografia

Come presentare i file all'utente

I file hanno delle proprietà, che sono il nome, il tipo, la lunghezza, la data di aggiornamento e ogni altra informazione che possa tornare utile all'utente per scegliere consapevolmente se scaricare il file oppure no. Questo vale soprattutto per i file lunghi, che richiedono tempo e impegnano la rete e il server. E' nell'interesse del gestore del sito WEB assicurare che i file scaricati corrispondano alle aspettative degli utenti.

Il modo più generale per presentare il tipo di un file è usare il tipo MIME. Potremo anche associare una icona che richiama la natura del file, ma il tipo MIME è l'informazione più completa che possiamo dare. A livello di interfaccia torna utile fornire anche una descrizione in prosa, almeno per i tipi di file più comuni. Questa funzione serve proprio allo scopo:

/*. string .*/  function MIMEtoHumanReadable(/*. string .*/ $mime)
{

    switch($mime) {
    case "text/plain": return "testo";
    case "text/html":  return "ipertesto";

    case "image/gif":
    case "image/png":
    case "image/bmp: return "immagine per punti";

    case "image/jpeg": return "immagine per punti di tipo fotografico";

    case "application/postscript": return "documento PostScript";
    case "application/msword": return "documento Microsoft Word";
    case "application/zip": return "file compattato";

    case "video/mpeg":
    case "video/quicktime":
    case "video/x-msvideo": return "filmato";

    case "application/octet-string": return "file binario";

    /* ecc. ecc. */

    default:
        trigger_error(__FUNCTION__ . ": missing case for MIME type $mime");
        return $mime;
    }
}

Ovviamente sono possibili altre soluzioni più generali e più sofisticate che sfruttano un array, oppure una tabella del DB per consentire aggiornamenti senza modificare il programma, e che magari supportino la lingua preferita dal navigatore e l'icona associata al tipo.

Come permettere il download all'utente

Supponiamo di avere i dati di un file in altrettante variabili:

# Nome originale del file
# (codifica UTF-8 di "Caffè Brillì.pdf"):
$file_name = "Caff\xC3\xA8 Brill\xC3\xAC.pdf";

# Suo tipo MIME:
$file_mime = "application/pdf";

# Collocazione effettiva del file sul server:
$file_path = "/home/private_dir/01/23/45/06.dat";

Qui ho scritto il nome del file in caratteri ASCII perché non ci siano ambiguità di rappresentazione di questo documento; nella pratica usando un text editor moderno si possono creare direttamente le stringhe UTF-8 in PHP.

Per la sola visualizzazione basta indicare il tipo MIME nella intestazione HTTP e poi inviare il contenuto binario del file:

function echoFile($fn) {
	$f = fopen($fn, "rb");
	if( $f === FALSE )
		throw new RuntimeException("cannot read file $fn");
	do {
		$chunk = fread($f, 4196);
		if( $chunk === NULL )
			break;
		echo $chunk;
	} while(TRUE);
	fclose($f);
}

header("Content-Type: $file_mime");
echoFile($file_path);

La funzione readfile() non va bene per inviare il file al client perché può causare un out of memory error se il file è più grande dello spazio in memoria disponibile per lo script (vedere PHP BUG 61636 per ulteriori info). Il rimedio proposto di disattivare l'output buffering con:

while( ob_get_level() )
    ob_end_flush();
readfile($fn);
	

funziona, ma per file lunghi intercorre un grande ritardo prima che il browser cominci a ricevere dati e proponga la dialog di salvataggio. Non rimane che scrivere una funzioncina come quella qui sopra per poter inviare file di arbitraria lunghezza e in modo indipendente dall'output buffering.

Quando il browser riceve questa risposta tenterà del suo meglio per visualizzare il file. Se il tipo MIME è tra quelli generalmente supportati nativamente dai browser (come testi puri e immagini GIF/PNG/JPEG) allora verrà visualizzato, altrimenti il browser può avvalersi anche di applicazioni esterne. Ad esempio, in Mozilla ci sono le configurazioni delle "Helper Applications" dove si possono aggiungere nuovi tipi MIME e rispettive applicazioni che li visualizzano.

Per suggerire il download come file piuttosto che la sua visualizzazione, il server deve indicare che la risposta contiene un "allegato". Questo richiede un ulteriore header Content-Disposition dove andremo a specificare anche il nome originale del file:

header("Content-Type: $file_mime");
header("Content-Disposition: attachment; filename=\"$file_name\"");
header("Content-Length: " . filesize($file_path));
echoFile($file_path);

Il parametro attachment farà aprire la finestra di dialogo per il salvataggio del file. Il suo contrario è inline che vuol dire: mostra il documento ed eventualmente consenti di salvarlo.

Comunque sia, il parametro filename=... suggerisce il nome del file da usare nel caso che l'utente voglia salvare il file su disco.

L'intestazione Content-Length permette al browser di conoscere in anticipo la dimensione finale del file. Inoltre il browser usa questo dato per mostrare la percentuale del download. Se il contenuto del file deve essere generato dinamicamente e la lunghezza non si conosce in anticipo, allora questa intestazione non va messa. Nei nostri esempi usiamo dei file, quindi filesize() ci darà il valore richiesto.

Abbiamo trascurato di specificare come codificare il nome del file: cosa succede se il nome del file contiene le virgolette doppie? come fa il browser a sapere quale charset usa? e come si comporta in generale il browser se il nome del file risulta non valido sul file system del client? Queste sono le questioni che andremo a precisare nei prossimi paragrafi e sono il vero grande problema.

La ricetta per gli impazienti

Browser diversi hanno comportamenti diversi quando il nome del file contiene dei caratteri non-ASCII. Questo spezzone di codice produce il download via HTTP di un file generico con nome codificato UTF-8:

$file_name = "Caffé Brillì.pdf";   # file name, UTF-8 encoded
$file_mime = "application/pdf";    # MIME type
$file_path = "absolute/or/relative/path/file";

header("Content-Type: $file_mime");
header("Content-Length: " . filesize($file_path));
$agent = $_SERVER["HTTP_USER_AGENT"];
if( is_int(strpos($agent, "MSIE")) ){ // MS IE <= 6
    # Remove reserved chars: :\/*?"<>|
    $fn = preg_replace('/[:\\x5c\\/*?"<>|]/', '_', $file_name);
    # Non-standard URL encoding:
    header("Content-Disposition: attachment; filename="
    . rawurlencode($fn));

} else if( is_int(strpos($agent, "Gecko")) ){
    # RFC 2231, 5987:
    header("Content-Disposition: attachment; filename*=UTF-8''"
    . rawurlencode($file_name));

} else if( is_int(strpos($agent, "Opera")) ) {
    # Remove reserved chars: :\/*{?
    $fn = preg_replace('/[:\\x5c\\/{?]/', '_', $file_name);
    # RFC 2231, 5987:
    header("Content-Disposition: attachment; filename*=UTF-8''"
    . rawurlencode($fn));

} else {
    # RFC 2616 ASCII-only encoding:
    $fn = mb_convert_encoding($file_name, "US-ASCII", "UTF-8");
    $fn = (string) str_replace("\\", "\\\\", $fn);
    $fn = (string) str_replace("\"", "\\\"", $fn);
    header("Content-Disposition: attachment; filename=\"$fn\"");
}

echoFile($file_path);

Purtroppo non tutti i problemi sono risolti da questo spezzone di codice. Una soluzione più rifinita richiede di considerare anche altre particolarità e comportamenti strani dei vari browser e altre questioni ancora rimangono poco chiare, come il comportamento rispetto al conflitto tipo MIME/estensione e il comportamento rispetto ai caratteri riservati del file system del client.

Una implementazione delle funzioni di download di file è disponibile nel package PHPLint, classe FileDownload; la funzione sendHeaders() permette di inviare l'header, mentre la funzione sendFile() permette di inviare il file.

Come codificare il nome del file

Negli esempi più banali di prima abbiamo semplicemente inserito il nome del file nell'header HTTP ignorando del tutto il problema della codifica del nome del file. Questo funziona solo se il nome del file contiene solo codici ASCII. In generale, invece, il nome del file è una stringa arbitraria espressa in un certo charset. Per fissare le idee, nel seguito useremo sempre l'UTF-8, che permette di codificare praticamente tutti gli alfabeti del mondo. Passiamo in rassegna le soluzioni possibili.

L'RFC 2616 descrive il protocollo HTTP/1.1. In questo documento si afferma che il valore del parametro filename deve essere codificato ASCII, oppure in ISO-8859-1, oppure ancora secondo la codifica "MIME header" del documento RFC 2047. Gli autori hanno trascurato di indicare come il browser dovrebbe fare per determinare quale di queste codifiche è stata usata, per cui una certa euristica si rende necessaria. Senza entrare nei dettagli di tutti questi documenti, vediamo come le diverse codifiche si applicano a un file di nome Caffè Brillì.pdf. Questo nome di file contiene due lettere accentate e pertanto non è ASCII.

Ho poi provato tutti gli algoritmi di codifica qui esaminati con il programma di test che viene citato al termine di questo articolo. Condizioni del test: il nome del file è quello degli esempi precedenti, codificato UTF-8. Si vuole verificare come si comportano i vari browser quando il nome del file contiene dei caratteri non-ASCII.


Browser/S.O. Raw MIME header Enhanced URL-Encoded
Chrome 31.0/Windows Vista
MSIE 9.0/Windows Vista No (3) No Sì (7)
MSIE 8.0/Windows Vista No (3) No No (6) Sì (7)
MSIE 6.0/Windows 98 No (3) No (4) No (5)
Firefox 25.0/Windows Vista No
Firefox 3.6/Windows Vista No
Firefox 3.6/Linux No
Mozilla 1.7.13/Linux Sì (1) No
Opera 9.01/Linux Sì (1) No (2) No
Opera 9.01/Windows 98 Sì (1) No (2) No

Risultati dei test sul trasferimento del nome file codificato UTF-8.


NOTE:

1. Mozilla e Opera sembrano avere abbracciato la tendenza corrente in Internet di considerare l'UTF-8 come la naturale estensione dell'ASCII. Di conseguenza il charset default non è ISO-8859-1, ma UTF-8.

2. Opera non interpreta correttamente la codifica MIME header e mostra un laconico "=.pdf". Evidentemente si è accorto di qualcosa, ma non gli è piaciuto tanto. Cosa vuole in realtà Opera?

3. Presume la codifica ISO-8859-1, per cui i caratteri UTF-8 diventano casuali.

4. Mostra un nome generato casualmente, come "CAPSSFDX".

5. Mostra il "basename" dell'URL, nel mio caso php-file-download-test.cgi.

6. Mostra il "basename" dell'URL ma con estensione PDF, cioè php-file-download-test.pdf.

7. Con la codifica non-standard URL-encoded, se l'estensione del nome del file manca, allora incomprensibilmente lo spazio codificato come %20 non viene riconvertito in spazio e rimane %20. Con questo browser è quindi sempre necessario fornire la corretta estensione.

Gestire caratteri speciali ed estensioni

Il compito del server è (dovrebbe essere...) consegnare al browser il nome del file esattamente così com'è, compresa la sua eventuale estensione. Se il sistema dove gira il browser adotta il meccanismo delle estensioni per assegnare un tipo ai file, allora sarà il browser a controllare la corrispondenza tra l'estensione e il tipo MIME: se l'estensione manca il browser l'aggiunge; se l'estensione c'è ma non corrisponde al tipo MIME allora può aggiungere l'estensione prevista oppure sostituirla a quella esistente (io propendo per il secondo meccanismo). Seguono esempi "patologici" nei quali il browser deve gestire la situazione:

Fatta questa premessa su come le cose dovrebbero andare, vediamo nella pratica come si comportano i vari browser. La situazione è piuttosto variegata e i browser tendono ad alterare arbitrariamente il nome del file ed espongono l'utente a rischi di sicurezza con la gestione delle estensioni. Sebbene non sia compito del server rispondere delle mancanze che sono invece lato client, esponiamo anche i possibili rimedi che il programmatore può adottare per facilitare la vita all'utente. Alcuni rimedi richiedono di estrapolare non solo il browser usato, ma anche il sistema operativo.


Attenzione! I rimedi che propongo cercano di ovviare ai difetti che ho fin qui riscontrato. E' possibile che la mia analisi sia incompleta. E' possibile che taluni caratteri o combinazioni di caratteri che qui non ho considerato possano causare problemi inaspettati. In definitiva, lato server possiamo tentare il possibile, ma la responsabilità finale sul come vengono gestiti i caratteri, le estensioni e la sicurezza in generale, rimangono a carico del browser e dell'utente.

Chrome 31. Rimpiazza i caratteri : / \ ? * " < > | con - (meno). Ignora il tipo MIME proposto e si basa unicamente sulla estensione, sicché se il tipo MIME è application/pdf e l'estensione è .txt, il file finale mantiene l'estensione sbagliata e viene poi aperto come testo. Rimedio. Se l'estensione manca o non corrisponde al tipo MIME, aggiungere l'estensione convenzionale (non tentare di sostituirla all'apparente estensione esistente, che potrebbe non essere affatto una estensione). Esempio: Panorama 11.dic diventa Panorama 11.dic.jpeg.

MSIE 9. Rimpiazza i caratteri : / \ ? * " < > | con _ (underscore). Ignora il tipo MIME proposto e si basa unicamente sulla estensione, sicché se il tipo MIME è application/pdf e l'estensione è .txt, il file finale mantiene l'estensione sbagliata e viene poi aperto come testo. Rimedio. Se l'estensione manca o non corrisponde al tipo MIME, aggiungere l'estensione convenzionale (non tentare di sostituirla all'apparente estensione esistente, che potrebbe non essere affatto una estensione). Esempio: Panorama 11.dic diventa Panorama 11.dic.jpeg.

MSIE 6 e 8. Non tollera i caratteri :\/*?"<>| Se anche uno solo di questi caratteri compare nel nome, tipicamente il browser sostituisce tutto il nome del file con un nome generato casualmente, ad esempio un criptico "CAEFGR34..pdf". Si salvano lo slash e il back-slash: se uno di questi appare, esso viene considerato separatore di pathfile, e quindi mantiene solo quello che sta a destra di questo carattere. In definitiva "Fattura 3/2006.pdf diventa "2006.pdf". Il tipo MIME non viene mai presentato all'utente, nè viene descritto in alcun modo il contenuto del file. Se aggiungiamo il fatto che tra le preferenze default del sistema operativo c'è quella di nascondere le estensioni dalla vista degli utenti non smaliziati, ecco che il nostro famigerato file virus.exe spacciato per un innocuo application/pdf viene salvato tranquillamente ed eseguito senza che l'utente possa conoscere né il tipo MIME, nè la reale estensione. Rimedi. Sostituire i caratteri vietati con underscore. Se l'estensione manca o non corrisponde al tipo MIME, aggiungere l'estensione convenzionale (non tentare di sostituirla all'apparente estensione esistente, che potrebbe non essere affatto una estensione). Ad esempio, l'immagine JPEG di nome Panorama:12/11/1980 12.00 deve diventare qualcosa come Panorama_12_11_1980 12.00.jpg

Mozilla 1.7.15/Linux. Sostituisce l'unico carattere riservato dal file system "/" con "-", mentre lascia intatti tutti gli altri caratteri. L'estensione del nome file, se presente, viene lasciata immutata anche se non corrisponde alla convenzione genericamente adottata sul sistema operativo sottostante. Però la dialog box per il salvataggio del file si basa solo sulla estensione per descrivere il tipo del file, anche se questa contrasta con il tipo MIME indicato. L'estensione rimane comunque sempre ben visibile. Rimedi. Se l'estensione manca o non corrisponde al tipo MIME, aggiungere l'estensione prevista. Ad esempio se una fotografia JPEG si chiama Panorama 12/11/1980 correggere in Panorama 12/11/1980.jpg

Opera 9.01/Linux. Considera i caratteri "/\:" come separatori di path, quindi elimina tutto quello che precede questo carattere; il nome del file risulta così troncato. La parentesi graffa aperta "{" ha un effetto strano: tutto quello che va da questo carattere in poi viene cancellato. Il carattere ? invece ha l'effetto opposto: da qui in poi la stringa viene cancellata. Perché Opera presenti tutti questi strani comportamenti su file system Unix-like non me lo spiego. Quando si salva il file, Opera controlla se l'estensione corrisponde al tipo MIME. Opera considera "estensione" tutto quello che segue l'ultimo punto fermo nel nome del file; pertanto se il nome del file include dei punti fermi (a parte quello della eventuale estensione) il nome del file potrebbe risultare troncato in modo imprevisto. Se il nome del file termina con una estensione che non corrisponde al tipo MIME, allora Opera elimina l'estensione ritenuta erronea e la sostituisce con quella prevista. Se il nome del file non contiene una estensione, Opera aggiunge quella prevista. Rimedi. Lato server, sostituire i caratteri riservati con underscore o altro carattere innocuo. Se il nome del file non ha estensione oppure ne ha una non adatta aggiungere l'estensione convenzionale. Secondo queste regole il nome Panorama 12/11/1980 diventa Panorama 12_11_1980.jpg

Opera 9.01/Windows. Non ho fatto prove esaustive, ma probabilmente va applicato il filtro sui caratteri riservati del file system Windows, e l'estensione prevista va aggiunta se mancante o non adatta.


Browser/S.O. Caratteri vietati Carattere
sostitutivo
Assicura corrispondenza
estensione/tipo MIME
Chrome 31.0/Windows Vista : / \ ? * " < > | - No
MSIE 9.0/Windows Vista : / \ ? * " < > | _ No
MSIE 8.0/Windows Vista : / \ ? * " < > | _ No
MSIE 6.0/Windows 98 : / \ ? * " < > |   No
Firefox 25.0/Windows Vista : / \ ? * " < > | _
Firefox 3.6/Windows Vista : / \ ? * " < > | _
Firefox 3.6/Linux / _ No
Mozilla 1.7.13/Linux / - No
Opera 9.01/Linux : / \ { ?  
Opera 9.01/Windows 98 N.D. N.D.

Risultati dei test su caratteri ed estensioni.


La tabella qui sopra riassume il comportamento dei vari browser provati rispetto ai caratteri presenti nel nome del file e rispetto alla corrispondenza tra estensione e tipo MIME. Mozilla si comporta bene per i caratteri: solo lo slash, unico carattere riservato in Linux, viene riconosciuto e sostituito con un meno. Opera su Windows e Firefox 3.6 su Windows e Linux si dimostrano molto rigorosi nella gestione delle estensioni e nella visualizzazione del tipo MIME corretto, salvando l'utente da brutte sorprese.

Come fare in pratica

Tra i vari browser che ho provato non c'è una soluzione comune valida per tutti e capace di preservare il nome del file UTF-8. Non rimangono che tre alternative:

Usare l'ASCII. Semplice da implementare, valido per tutti i browser, ma supporta solo i nomi file composti da caratteri ASCII. Tutti i codici non-ASCII stampabili vanno eliminati oppure sostituiti con un segnaposto. Le estensioni dei nomi di file non vanno toccate, ma ricordare di fornire sempre il giusto tipo MIME. Quindi, niente vocali accentate, niente alfabeti dei paesi dell'est. Anche turchi, coreani, giapponesi e cinesi incontreranno qualche problema.

Offrire un comportamento differenziato in base al browser. Basta guardare la variabile $_SERVER['HTTP_USER_AGENT']: se contiene MSIE oppure Gecko oppure Opera abbiamo identificato il browser. Per MSIE potremo usare URL-Encode, mentre per gli altri due potremo usare la codifica enhanced. Inoltre MSIE dà la precedenza alle estensioni rispetto al MIME type: per proteggere i nostri utenti, nel caso potremmo forzare l'estensione corretta al file prima di inviarlo al browser.

Configurazione dell'utente. L'automatismo di prima dovrebbe soddisfare la maggior parte degli utenti. Gli utenti più smaliziati potrebbero gradire una interfaccia di configurazione del download per stabilire quale codifica usare tra quelle che abbiamo proposto qui. Tra i parametri di configurazione possiamo includere anche il filtro dei caratteri speciali, e magari possiamo lasciare all'utente stesso la possibilità di definire questi caratteri speciali. Naturalmente l'interfaccia deve permettere di provare le impostazioni correnti, in modo che si possa procedere per tentativi. Il vantaggio di questa soluzione è che la nostra applicazione WEB risulta già pronta quando i vari browser si saranno messi daccordo. Il programma di test che propongo alla fine di questo articolo mostra un esempio di interfaccia che offre all'utente la possibilità di configurare le preferenze per il download e fare un test. In attesa che i produttori di browser trovino un accordo, questa è la soluzione più completa e rigorosa.

La trappola delle sessioni

Spesso il download dei file dipende delle autorizzazioni dell'utente, e quindi viene coinvolto il login dell'utente e la sessione dell'utente per stabilire se egli è autorizzato a scaricare quel file. Inoltre, spesso le informazioni della sessione contengono la chiave primaria del DB, il path del file sul server o altre informazioni che non è bene inviare al client e che permettono al nostro programma di individuare il file che l'utente è autorizzato a scaricare. Vediamo allora quali interazioni ci sono tra le sessioni del PHP e il nostro il download dei file.

Il comportamento default del PHP quando si genera una sessione con session_start() è di inviare un insieme di headers HTTP che impediscono il caching e lo storing dei files. Questo interferisce con il salvataggio dei file scaricati, che potrebbe essere del tutto vietato dal browser. Ad esempio, usando la configurazione PHP default, questo programma

<?php
session_start();
echo "ciao";
?>
produce questa risposta:
HTTP/1.1 200 OK
Date: Thu, 26 Oct 2006 15:04:49 GMT
Server: Apache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Pragma: no-cache
X-Powered-By: PHP/5.1.1
Set-Cookie: PHPSESSID=628fd5de0dba53c92f66e6c8c1e14627; path=/
Transfer-Encoding: chunked
Content-Type: text/html

4
ciao
0

La parte evidenziata in grassetto è il problema. No-cache significa che il file verrà scaricato ogni volta che l'utente richiede l'URL. No-store significa che il browser deve evitare di salvare il file scaricato nell'HD dell'utente per mantenere la cache, ma il browser potrebbe interpretare in modo restrittivo questa direttiva vietando del tutto il salvataggio dei dati. Per eliminare queste righe ci sono due modi:

Caching

Gli utenti ripetono spesso il download di uno stesso file: vogliono rivedere un certo documento; hanno dimenticato dove hanno salvato il download precedente; hanno dimenticato di averlo già scaricato; hanno interrotto distrattamente il download e vogliono riprendere il download interrotto; si divertono a cliccare ripetutamente sul link; varie ed eventuali. Il caching secondo l'HTTP (RFC 2616) è un tema piuttosto complicato. La cache viene implementata dal browser e dal proxy server (se presente). Il server può specificare con quali modalità le informazioni mantenute nella cache devono essere gestite. Il supporto della cache lato server aiuta a risparmiare banda e rende più veloce la navigazione. Quando sono coinvolti file particolarmente grandi, la gestione corretta della cache è indispensabile. Diamo qualche suggerimento al riguardo, rimandando all'RFC 2616 per i dettagli.

Nessun provvedimento. E' quello che abbiamo fatto finora. Il download viene richiesto dall'utente cliccando su di un'ancora. Il browser o il proxy, in base a una qualche impostazione di preferenza, decide se è ora di ripetere la richiesta dello stesso URL oppure ripresentare all'utente il file già disponibile nella cache. Questo meccanismo può creare problemi se lo stesso URL (al quale corrisponde il nostro programma di download) viene riutilizzato più volte per scaricare file diversi; spesso il file specifico da scaricare di volta in volta viene deciso dal programma con una sua logica interna, ma dal punto di vista del browser l'URL dal quale proviene il file è unico, per cui è anche unica l'entrata della cache associata ad esso. Trucco: mettere un parametro nell'URL che sia associato univocamente al file specifico, per esempio il suo nome, la sua PK nel data base, il suo digest di controllo o il suo path assoluto sul server. Per esempio:

http://www.miosito.it/download.php?file=pippo.pdf

Notare che il parametro file=pippo.pdf deve essere adeguatamente validato dal programma, altrimenti consentiremmo di scaricare file arbitrari fuori dal controllo del nostro programma.

Vietare il caching. Basta aggiungere l'header header("Cache-Control: no-cache"); Questo costringe il browser a ripetere sempre la richiesta GET del file per intero, disabilitando ogni meccanismo di caching.

Gestire richieste condizionali. Il browser (o il proxy) può ripetere la richiesta GET di un file subordinata al fatto che il file in questione sia stato aggiornato rispetto alla data salvata nella cache. In pratica il browser chiede: mandami il file solo se aggiornato rispetto alla volta precedente. La richiesta condizionale si riconosce per via della presenza dell'header $_SERVER['HTTP_IF_MODIFIED_SINCE'] contenente la data di riferimento del browser. Il server risponde con il codice di stato 304 (Not Modified) oppure con il codice 200 e il file a seguire.

Gestire richieste parziali di file. A volte il browser richiede solo la parte iniziale di un file, giusto per presentare un'anteprima all'utente, e poi prosegue nello scaricare il resto con richieste successive. Lo stesso meccanismo è utile se il trasferimento è stato interrotto per un qualche motivo e il browser vuole salvare la parte già disponibile. Questo tipo di richiesta si riconosce per via della presenza dell'header $_SERVER['HTTP_RANGE'] che contiene il range (o i range) che interessano. Il server risponderà con un 206 alla richiesta parziale, oppure con un codice di errore se la richiesta non è valida.

Il programma di test

Per verificare il comportamento dei browser alle varie codifiche e il comportamente nella gestione di tipo MIME ed estensione, ho scritto un piccolo programma di test che si trova qui:

php-file-download-test.cgi

Questo programma è utile sia per eseguire le prove sulla propria combinazione browser/sistema operativo, sia come possibile modello di implementazione dell'interfaccia utente, sia come sorgente pronto all'uso.

Il programma offre in fondo alla pagina l'ancora per scaricare il sorgente di sè stesso. Per eseguire i test procede nel modo seguente.

Un nome di file viene fornito in una casella di input a linea singola e memorizzato dal programma come stringa UTF-8.

Poi bisogna scegliere quale charset usare per inviare il nome del file al client. Ci sono tre possibilità l'UTF-8 è ovviamente quella preferibile; l'ISO-8859-1 dovrebbe essere intepretato per default dai browser più antiquati; l'ASCII è l'ultima risorsa. All'atto del download il nome del file viene convertito nel charset scelto prima di essere inviato al browser. Per i charset diversi da UTF-8 ovviamente c'è la possibilità che qualche carattere non sia convertibile: i caratteri non convertibili vengono rappresentati con l'underscore.

Alcuni browser fanno i capricci se il nome del file contiene certi caratteri. L'opzione di filtro, quando attiva, li converte in underscore.

Infine possiamo scegliere la codifica da usare, come abbiamo descritto in questo articolo.

Infine, il bottone Try Download rimanda alla pagina del download. La pagina del download mostra le linee di header che verranno usate, l'ancora per scaricare effettivamente il file e l'ancora per ritornare alla maschera di configurazione del programma. Il file scaricato è un PDF di 5 KB.

Le cose da provare con ogni combinazione di browser/sistema sono:

Bibliografia


Umberto Salsi
Commenti
Contatto
Mappa
Home / Indice sezione
Still no comments to this page. Use the Comments link above to add your contribute.