#!/bin/php -c.. |'); define("INVALID_CHARS_REGEX", '/[:\\x5c\\/*?"<>|]/'); /*. string .*/ function remove_ctrls(/*. string .*/ $s) # Remove CTRLs chars ASCII codes 0-31,127. { return preg_replace("/[\\000-\\037\\177]/", "", $s); } /*. string .*/ function escape_doublequote_backslash(/*. string .*/ $s) { $s = (string) str_replace("\\", "\\\\", $s); $s = (string) str_replace("\"", "\\\"", $s); return $s; } /*. string .*/ function get_extension(/*. string .*/ $fn) /* Returns the extension from the file name $fn. Valid extensions are any sequence of alphanumerical ASCII chars following the last period, ranging from one char up to 4. Returns NULL if no valid extension was found. Examples: "SOMETHING.XyZ" --> "XyZ" "SOME.THING.X" --> "X" "SOMETHING" --> NULL (missing extension) "SOMETHING..." --> NULL (empty extension after last period) "Today 12.08.2006" --> "2006" "photo.album" --> NULL (invalid extension: too long) */ { $i = strrpos($fn, "."); if( $i === FALSE ) return NULL; $ext = substr($fn, (int) $i + 1); if( preg_match("/^[a-zA-Z0-9]{1,4}\$/D", $ext) == 0 ) return NULL; return $ext; } $extensions = array( # See http://www.webmaster-toolkit.com/mime-types.shtml # for a complete list. "application/octet-stream", "bin", "application/octet-stream", "dat", "text/plain", "txt", "text/plain", "text", "text/html", "htm", "text/html", "html", "image/gif", "gif", "image/png", "png", "image/jpeg", "jpg", "image/jpeg", "jpeg", "application/pdf", "pdf", "application/ps", "ps", "video/avi", "avi", ); /*. array[int]string .*/ function extension_to_mimes(/*. string .*/ $ext) { global $extensions; $mime_list = /*.(array[int]string).*/ array(); $n = count($extensions); for($i=0; $i<$n; $i+=2) if( $extensions[$i+1] === $ext ) $mime_list[] = $extensions[$i]; return $mime_list; } /*. array[int]string .*/ function mime_to_extensions(/*. string .*/ $mime) { global $extensions; $ext_list = /*.(array[int]string).*/ array(); $n = count($extensions); for($i=0; $i<$n; $i+=2) if( $extensions[$i] === $mime ) $ext_list[] = $extensions[$i+1]; return $ext_list; } /*. string .*/ function ensure_proper_extension( /*. string .*/ $fn, /*. string .*/ $mime) /* Returns the file name $fn with a proper extension corresponding to the MIME type $mime. If the extension isn't valid or is missing, add one. If the MIME type is unknown, do nothing and returns the filename as is. */ { $exp = mime_to_extensions($mime); if( count($exp) == 0 ) return $fn; $cur = strtolower( get_extension($fn) ); if( in_array($cur, $exp) ) return $fn; return $fn . "." . $exp[0]; } $charsets = array("US-ASCII", "ISO_8859-1", "UTF-8"); class Status { public $file_name = FILE_NAME; /* filename, UTF-8 */ public $charset = 0; /* global $charsets[$this-charset] is the required charset for the filename to be sent via HTTP */ public $filter = TRUE; /* remove special chars */ public $ensure_extension = TRUE; public $encoding = 0; } /*. string .*/ function build_self_url(/*. Status .*/ $s, /*. string .*/ $op) { return $_SERVER["PHP_SELF"] . "?filename=" . rawurlencode($s->file_name) . "&charset=" . $s->charset . ($s->filter? "&filter=1":"") . ($s->ensure_extension? "&ensure_extension=1":"") . "&encoding=" . $s->encoding . "&op=" . $op; } /*. Status .*/ function get_status() { $s = new Status(); if( isset($_REQUEST['filename']) ){ $fn = remove_ctrls( (string) $_REQUEST['filename'] ); $fn = mb_convert_encoding($fn, "UTF-8", "UTF-8"); $s->file_name = $fn; } else { return $s; } if( isset($_REQUEST['charset']) ){ $i = (int) $_REQUEST['charset']; if( $i >= 0 and $i < count($GLOBALS['charsets']) ) $s->charset = $i; } $s->filter = isset( $_REQUEST['filter'] ); $s->ensure_extension = isset( $_REQUEST['ensure_extension'] ); if( isset($_REQUEST['encoding']) ){ $i = (int) $_REQUEST['encoding']; if( $i >= 0 and $i < 4 ) $s->encoding = $i; } return $s; } /*. void .*/ function page_header(/*. string .*/ $title) { header("Content-Type: text/html; charset=UTF-8"); header("Cache-Control: no-cache"); echo "$title", "", "

$title

"; } /*. void .*/ function page_footer() { echo "

File Download Test Program

"; } /*. void .*/ function echo_radio( /*. string .*/ $name, /*. int .*/ $value, /*. int .*/ $cur_value, /*. string .*/ $text) { $label = $name . "_" . $value; echo " "; } /*. void .*/ function echo_checkbox( /*. string .*/ $name, /*. int .*/ $value, /*. int .*/ $cur_value, /*. string .*/ $text) { $label = $name . "_" . $value; echo " "; } /*. void .*/ function echo_submit(/*. string .*/ $text, /*. string .*/ $func) { echo ""; } /*. void .*/ function page_menu(/*. Status .*/ $s) { page_header("Configurazione File Download"); echo "
"; echo "
"; echo "Charset per il nome del file:
", "
"; foreach( $GLOBALS['charsets'] as $k => $v ){ echo_radio("charset", $k, $s->charset, $v); echo "

"; } echo "

"; echo "
"; echo "Codifica per il nome del file:
", "
"; echo_radio("encoding", 0, $s->encoding, "Raw (no encoding)"); echo "

"; echo_radio("encoding", 1, $s->encoding, "MIME Header (RFC 2047)"); echo "

"; echo_radio("encoding", 2, $s->encoding, "Enhanced filename* parameter (RFC 2231)"); echo "

"; echo_radio("encoding", 3, $s->encoding, "URL Encoded (not standard)"); echo "

"; echo "

"; echo "
"; echo_checkbox("filter", 1, $s->filter? 1:0, "Filtra i caratteri " . htmlspecialchars(INVALID_CHARS) . " e convertili in _"); echo "

"; echo_checkbox("ensure_extension", 1, $s->ensure_extension? 1:0, "Assicura una estensione appropriata al tipo MIME del file"); echo "

"; echo "Nome del file: file_name), "\">

"; echo "

"; echo_submit("Download", "page_download"); echo "
"; echo "
"; echo "

Note

"; echo "Impostazioni consigliate. "; $agent = isset($_SERVER["HTTP_USER_AGENT"])? $_SERVER["HTTP_USER_AGENT"] : ""; if( is_int(strpos($agent, "MSIE")) ){ echo <<< EOT Il browser che stai usando è stato identificato come Microsoft Internet Explorer. Per questo browser le impostazioni consigliate sono: charset UTF-8, codifica URL Encoded, filtro abilitato, assicura estensione... abilitato. EOT; } else if( is_int(strpos($agent, "Gecko")) ){ echo <<< EOT Il browser che stai usando è stato identificato come Gecko (Mozilla, Firefox). Per questo browser le impostazioni consigliate sono: charset UTF-8, codifica Enhanced filename* parameter, filtro disabilitato, assicura estensione... abilitato. EOT; } else if( is_int(strpos($agent, "Opera")) ){ echo <<< EOT Il browser che stai usando è stato identificato come Opera. Per questo browser le impostazioni consigliate sono: charset UTF-8, codifica Enhanced filename* parameter, filtro abilitato, assicura estensione... disabilitato. EOT; } else { echo <<< EOT Il tuo browser "$agent" non è riconosciuto da questo programma. L'impostazione consigliata in questo caso è: charset ASCII, codifica raw, filtro abilitato, assicura estensione abilitato. EOT; } /* echo "

Il bottone Salva qui sotto in realtà", " non fa nulla, aggiorna solo la pagina. Puoi passare direttamente", " al test più sotto.\n", "

"; echo_submit("Salva", "page_save"); echo "
\n
\n"; */ echo "Una volta impostata la configurazione, prova un nome di file", " `patologico' per eseguire il test.", " Spesso i browser hanno problemi con questi caratteri, ai quali", " attribuiscono un significato speciale:
", "SPAZIO ! \" # \$ % & ' () * + , - . / : ; < = > ? @ [ \\ ] ^ _ ` { | } ~", "
", " Il file di test ti verrà inviato con questo nome", " per verificare se le impostazioni sono adatte al tuo browser.

"; echo <<< EOT

Problemi? Se non riesci ad ottenere un risultato decente, prova le impostazioni suggerite qui sopra. L'impostazione charset ASCII, filtro attivo, codifica raw dovrebbe funzionare anche con i browser più antiquati. EOT; echo <<< EOT

Sorgente di questo programma. Il sorgente di questo programma di test è scritto nel linguaggio PHP. Il meccanismo per il download del sorgente usa... sè stesso! basta cliccare sull'ancora qui sotto. Hai impostato bene la configurazione? ;-)

EOT; echo "
SCARICA SORGENTE
"; echo <<< EOT

Riferimenti. Per spiegazioni più articolate sul meccanismo di download dei file via WEB, leggi l'articolo PHP File Download (www.icosaedro.it/articoli/php-file-download.html). EOT; page_footer(); } /*. array[int]string .*/ function build_http_headers( /*. Status .*/ $s, /*. string .*/ $filename, /*. string .*/ $mime, /*. string .*/ $path) { $fn = $filename; # Convert filename to the requested charset: $fn_charset = $GLOBALS['charsets'][$s->charset]; $fn = mb_convert_encoding($fn, $fn_charset, FILE_NAME_ENCODING); # Filter invalid characters: if( $s->filter ){ $fn = preg_replace(INVALID_CHARS_REGEX, '_', $fn); } # Ensure proper extension: if( $s->ensure_extension ){ $fn = ensure_proper_extension($fn, $mime); } # Generate filename[*]=... HTTP parameter: switch($s->encoding){ case 0: # Raw encoding: $fn = escape_doublequote_backslash($fn); $fn = "filename=\"$fn\""; break; case 1: # MIME header encoding (RFC 2047): $fn = "filename=\"=?$fn_charset?B?" . base64_encode($fn) . "?=\""; # FIXME: RFC 2047 states that there is a 75 chars length limit; # no problems with 100+ on Mozilla. break; case 2: # Enhanced encoding (RFC 2231): $fn = "filename*=$fn_charset''" . rawurlencode($fn); break; case 3: $fn = "filename=" . rawurlencode($fn); break; default: exit("unexpected encoding " . $s->encoding); } $a[0] = "Content-Type: $mime"; $a[1] = "Content-Disposition: attachment" #. "; size=" . filesize($path) # <-- FIXME: not supported (2183) . "; " . $fn; $a[2] = "Content-Length: " . filesize($path); $a[3] = "Cache-Control: no-cache"; # FIXME return $a; } /*. string .*/ function string_to_literal(/*. string .*/ $s) { return '"' . addcslashes($s, "\000..\037\"\${\177..\377") . '"'; } /*. void .*/ function page_download(/*. Status .*/ $s) { page_header("Configurazione - Download del File di Test"); echo "

Il file di test è un documento PDF di circa 5 KB.

"; $a = build_http_headers($s, $s->file_name, FILE_TYPE, FILE_PATH); echo "Header HTTP che verrà usato:
"; foreach($a as $v) echo "header(" . htmlspecialchars( string_to_literal($v) ), ");
\n"; echo "
"; echo "SCARICA IL FILE DI TEST
", " Alcuni browser si comportano male nella gestione dei caratteri,", " dei tipi o delle estensioni e propongono di salvare il file", " con un nome non adatto. Verifica che:", " Nota che il nome potrebbe essere intenzionalmente modificato", " dal programma a seconda delle opzioni di filtro che hai abilitato.

"; echo "

RIPROVA CON NUOVI PARAMETRI
", " Se il risultato non è soddisfacente, prova con altri parametri.", " La maschera per la configurazione riporta in fondo", " le impostazioni suggerite in base al browser che stai usando."; page_footer(); } /*. void .*/ function download_file( /*. Status .*/ $s, /*. string .*/ $file_name, /*. string .*/ $file_type, /*. string .*/ $file_path) { $a = build_http_headers($s, $file_name, $file_type, $file_path); foreach($a as $v) header($v); readfile($file_path); } # # MAIN # # Sets the char that substitute those that cannot be converted toward # the intended charset: mb_substitute_character(ord("_")); # Restore info from sticky form: $s = get_status(); # # Request handler: # if( $_SERVER['REQUEST_METHOD'] === "GET" && isset($_GET['op']) ){ $op = (string) $_GET['op']; } else if( $_SERVER['REQUEST_METHOD'] === "POST" ){ $op = NULL; foreach( /*. (array[string]string) .*/ $_POST as $k => $v ){ if( substr($k, 0, 7) === "button_" ){ $op = substr($k, 7); break; } } if( $op === NULL ){ trigger_error("missing button_* in POST"); $op = "page_menu"; } } else { $op = "page_menu"; } switch($op){ case "page_menu": page_menu($s); break; case "page_save": page_menu($s); break; case "page_download": page_download($s); break; case "download_file": download_file($s, $s->file_name, FILE_TYPE, FILE_PATH); break; case "download_source": $fn = basename( $_SERVER["SCRIPT_FILENAME"] ); $s->file_name = $fn; download_file($s, $fn, "text/plain", $fn); break; default: page_menu($s); break; } ?>