Capita frequentemente su questo forum di ricevere sollecitazioni in merito ai processi di upload di file tramite PHP. Molto spesso questi problemi sono dovuti ad un'insufficiente conoscenza di questa materia. Ho pensato quindi di scrivere questa breve pillola per chiarire i concetti principali e terminerò postandovi la mia classe Upload, in grado di gestire oltre al caricamento dei file anche diverse funzioni relative alla sicurezza ed alla gestione degli errori.
1. Configurazione di PHP
Ma innanzitutto vediamo alcune direttive importanti del file php.ini che potrebbero condizionare l'esito del nostro upload:
post_max_size: di default é settato a 8M
upload_max_filesize: di default é settato a 2M
Se la configurazione di php non é stata modificata potremo quindi caricare dei file aventi una dimensione massima di 2M. Per file di dimensioni maggiori dovremo considerare l'utilizzo di un sistema ftp.
2. Il form
Iniziamo da un esempio di form.
Codice PHP:
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="700000" />
<input type="file" name="upfile" value="" />
<input type="hidden" name="control" value="upload" />
<input type="submit" value="upload" />
</form>
Iniziamo dalla dichiarazione del form. Il metodo dovrà essere ovviamente POST ed é fondamentale che l'attributo enctype sia multipart/form-data.
Il primo input é facoltativo e serve a definire una dimensione massima del file che verrà caricato espressa in byte; chiaramente questo valore avrà senso solo se inferiore al valore di upload_max_filesize del php.ini. La dichiarazione MAX_FILE_SIZE deve avvenire prima dell'input relativo al file.
Il secondo input é quello relativo al file appunto. L'attributo name ci permetterà in seguito di manipolare l'array S_FILES, che come vedremo é ricco di informazioni.
Il terzo input é facoltativo. Semplicemente invio un campo nascosto con un nome e un valore che mi permetteranno in seguito di verificare che il file arrivi davvero dal mio form. E' un po' una ridondanza in quanto, come vedremo, la funzione is_uploaded_file() serve già a verificare che il file provenga da una richiesta HTTP POST e quindi può essere considerato legittimo. Ma con i balordi che ci sono in giro, non si é mai tropo prudenti.
3. L'array $_FILES
Anche se abbiamo utilizzato il metodo POST, ritroveremo le informazioni relative al nostro upload nell'array $_FILES.
Come attributo name abbiamo utilizzato upfile per il nostro file, dunque avremo le seguenti informazioni:
$_FILES['upfile']['name'] : E' il nome originale del file.
$_FILES['upfile']['tmp_name] : Contiene il nome del file temporaneo. Inizialmente il nostro file viene caricato nella cartella temporanea, che in assenza di altre direttive corrisponde alla cartella temporanea di sistema.
$_FILES['upfile']['size'] : La dimensione del file espressa in byte.
$_FILES['upfile']['error'] : Restituisce un codice numerico di errore. Per ogni numero, PHP dispone di costanti predefinite che contengono questo valore; L'utilizzo di queste costanti renderà più leggibile il codice. Vediamo questi errori:
0 = UPLOAD_ERR_OK. Il file é stato caricato correttamente.
1 = UPLOAD_ERR_INI_SIZE. La dimensione del file supera il valore impostato nella specifica direttiva di php.ini
2 = UPLOAD_ERR_FORM_SIZE. La dimensione del file supera il valore impostato nel form tramite MAX_FILE_SIZE.
3 = UPLOAD_ERR_PARTIAL. Il file é stato inviato solo parzialmente.
4 = UPLOAD_ERR_NO_FILE. Il file non é stato inviato.
6 = UPLOAD_ERR_NO_TMP_DIR. Mancanza di una directory temporanea.
7 = UPLOAD_ERR_CANT_WRITE. Impossibile scrivere su disco.
4. Controlli e finalizzazione dell'upload
Il primo controllo che possiamo fare é verificare la presenza del campo di controllo:
Codice PHP:
if($_POST['control'] == “upload”)
In seguito controlleremo se il file caricato nella directory temporanea é leggittimamente inviato dal protocollo HTTP
Codice PHP:
if(is_uploaded_file($_FILES['upfile']['tmp_name']))
Inoltre verifichiamo l'assenza di errori:
Codice PHP:
if($_FILES['upfile']['error'] == UPLOAD_ERR_OK)
Se queste minime condizioni vengono soddisfatte, si potrà trasferire il file temporaneo nella directory di destinazione.
Codice PHP:
move_uploaded_file($_FILES['upfile']['tmp_name'], “./destinazione/” . $_FILES['upfile']['name']);
Personalmente proporrei ancora un controllo come minimo:
Se il nome del file esiste già nella cartella di destinazione, il file presente verrà sovrascritto. Se non desideriamo questo comportamento, prima di spostare il file dovremo verificare la cartella di destinazione.
Codice PHP:
$files = scandir(“./destinazione”);
if(in_array($_FILES['upfile']['name'], $files))
{
//non spostare il file
}
5. La mia classe Upload
Vi posto la mia classe Upload seguita da un commento
Codice PHP:
class Upload
{
public $SavePath;
public $ErrorReport;
protected $file = array();
protected $AllowedExtensions = array();
protected $SecurityPostName;
protected $SecurityPostKey;
protected $HiddenSecurity;
protected $OverWrite;
public function __construct($path, $name, $overwrite = TRUE, $extensions = "", $PostName ="", $PostKey = "")
{
$this->SavePath = $path;
$this->file = $_FILES[$name];
$this->OverWrite = $overwrite;
if ($extensions)
{
$this->AllowedExtensions = $this->ParseExtensions($extensions);
}
else
{
$this->AllowedExtensions = FALSE;
}
if($PostName AND $PostKey)
{
$this->SecurityPostKey = $PostKey;
$this->SecurityPostName = $PostName;
$this->HiddenSecurity = TRUE;
}
else
{
$this->HiddenSecurity = FALSE;
}
$this->ErrorManagement();
}
private function ParseExtensions($string)
{
$ExtensionsArray = explode(",", $string);
return $ExtensionsArray;
}
private function FileExtention()
{
$exp = explode(".", $this->file['name']);
$exp = array_reverse($exp);
return $exp[0];
}
protected function ErrorManagement()
{
if ($this->HiddenSecurity)
{
if ($_POST[$this->SecurityPostName] != $this->SecurityPostKey)
{
$this->ErrorReport = "Tentativo non permesso di upload - possibile tentativo di forzatura";
return FALSE;
}
}
if ($this->AllowedExtensions)
{
if (!in_array($this->FileExtention(), $this->AllowedExtensions))
{
$this->ErrorReport = "Estensione file non accettata";
return FALSE;
}
}
if (!$this->OverWrite)
{
$files = scandir($this->SavePath);
if(in_array($this->file['name'], $files))
{
$this->ErrorReport = "Il nome del file caricato esiste già nella cartella di destinazione";
return FALSE;
}
}
switch($this->file['error'])
{
case UPLOAD_ERR_OK:
$this->ErrorReport = "File caricato correttamente";
$this->SaveUploadFile();
break;
case UPLOAD_ERR_INI_SIZE:
$this->ErrorReport = "Il file supera la dimensione massima impostata nel file php.ini (direttiva upload_max_filesize)";
break;
case UPLOAD_ERR_FORM_SIZE:
$this->ErrorReport = "Il file supera la dimensione massima impostata nel form";
break;
case UPLOAD_ERR_PARTIAL:
$this->ErrorReport = "Il file é stato caricato solo parzialmente";
break;
case UPLOAD_ERR_NO_FILE:
$this->ErrorReport = "Nessun file é stato caricato";
break;
case UPLOAD_ERR_NO_TMP_DIR:
$this->ErrorReport = "Nessuna cartella temporanea impostata";
break;
case UPLOAD_ERR_CANT_WRITE:
$this->ErrorReport = "Impossibile scrivere sul disco";
break;
}
}
protected function SaveUploadFile()
{
if(is_uploaded_file($this->file['tmp_name']))
{
move_uploaded_file($this->file['tmp_name'], $this->SavePath . $this->file['name']);
return TRUE;
}
else
{
$this->ErrorReport = "Tentativo non permesso di upload - possibile tentativo di forzatura";
return FALSE;
}
}
}
Iniziamo dagli argomenti passati dal costruttore:
$pach = Il percorso dove verrà salvato il file, deve terminare con /
$name = L'attributo name del file nel form (nel nostro caso "upfile").
$overwrite (facoltativo) = TRUE di default, significa che se il nome file esiste già nella destinazione, il file verrà sovrascritto. Impostando su FALSE verrà interrotto il processo e restituito un errore tramite la proprietà ErrorReport.
$extensions (facoltativo) = Di default non contiene nulla, dunque vengono accettate tutte
le estensioni. Se si volessero accettare solo certe estensioni, basta inserirle separandole con la virgola ("jpg,png,gif").
$PostName (facoltativo) = Il name dell'input di controllo (nel nostro caso "control").
$PostKey (facoltativo) = Il valore dell'input di controllo (nel nostro caso "upload").
Il costruttore, alla fine, chiama il metodo ErrorManagement. Questo metodo chiamerà poi, solo in assenza di errori, il metodo SaveUploadFile(). Diversamente valorizzerà la proprietà ErrorReport e teminerà il codice senza fare altro.
Dunque il file upload.php nel quale verremo indirizzati dal form avrà questo aspetto
Codice PHP:
include 'upload.class.php';
$upload = new Upload(“./uploads/”, “upfile”, TRUE, “jpg,gif,png”, “control”, “upload”);
// Eventualmente
echo $upload->ErrorReport;