ConFoo Montreal 2017 Calling for Papers

Metodo POST per caricamento di file

Questa caratteristica permette di caricare sia file di testo che binari. Utilizzando le funzioni di PHP per l'autenticazione e manipolazione dei file, è possibile avere pieno controllo su chi ha i permessi per caricare un file e su ciò che deve essere fatto una volta che il file è stato caricato.

Il PHP è in grado di ricevere upload di file da qualsiasi browser compatibile con la RFC-1867.

Nota: Note relative alla configurazione

Si vedano i parametri file_uploads, upload_max_filesize, upload_tmp_dir, post_max_size e max_input_time nel php.ini

Si noti che PHP permette l'upload di file con metodo PUT come utilizzato dai programmi Netscape Composer e W3C Amaya. Si veda Supporto per metodo PUT per maggiori dettagli.

Example #1 Form di caricamento file

La schermata di caricamento di un file può essere costruita con una form particolare, di questo tipo:

<!-- Tipo di codifica dei dati, DEVE essere specificato come segue -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
    <!-- MAX_FILE_SIZE deve precedere campo di input del nome file -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    <!-- Il nome dell'elemento di input determina il nome nell'array $_FILES -->
    Send this file: <input name="userfile" type="file" />
    <input type="submit" value="Send File" />
</form>

L'__URL__ dell'esempio precedente deve essere sostituito con il puntamento ad un file PHP.

Il campo nascosto MAX_FILE_SIZE (misurato in byte) deve precedere il campo di input del file, ed il suo valore indica la dimensione massima di file accettata. Questa è un'informazione per il browser, ma anche il PHP lo verifica. E' facile aggirare questa impostazione sul browser, quindi non fate affidamento sul fatto che il navigatore si comporti come desiderato! L'impostazione PHP lato server per la dimensione massima non può comunque essere aggirata. Tuttavia si può comunque inserire MAX_FILE_SIZE per evitare all'utente di attendere il trasferimento di un file prima di scoprire che è di dimensioni eccessive.

Nota:

Accertarsi che il form di upload abbia l'impostazione enctype="multipart/form-data" altrimentio non funzionerà.

A partire dal PHP 4.1.0 esiste la variabile globale $_FILES. (Utilizzare $HTTP_POST_FILES nelle versioni precedenti). Queste matrici contengono tutte le informazioni sui file inviati.

Di seguito verrà illustrato il contenuto di $_FILES nel caso dell'esempio precedente. Si noti che si assume come nome del file inviato userfile, come nell'esempio precedente. Nella realtà questo può assurmere nome.

$_FILES['userfile']['name']

Il nome originale del file sulla macchina dell'utente.

$_FILES['userfile']['type']

Il mime-type del file, se il browser fornisce questa informazione. Un esempio potrebbe essere "image/gif". Questo mime type comunque non è controllato sul lato PHP e quindi non ci si deve fidare di questo valore.

$_FILES['userfile']['size']

La dimensione, in bytes, del file caricato.

$_FILES['userfile']['tmp_name']

Il nome del file temporaneo in cui il file caricato è salvato sul server.

$_FILES['userfile']['error']

Il codice di errore associato all'upload di questo file. Questo elemento è stato aggiunto nella versione 4.2.0 di PHP.

I file sono, di default, salvati in una directory temporanea sul server, a meno che un diverso percorso sia specificato nella direttiva upload_tmp_dir nel file php.ini. La directory del server predefinita può essere cambiata impostando la variabile di ambiente TMPDIR in cui è in esecuzione PHP. Non è possibile impostare questa variabile utilizzando la funzione putenv() da uno script PHP. Questa variabile di ambiente può anche essere usata per assicurarsi che anche altre operazioni stiano lavorando sui file caricati.

Example #2 Verifica dell'upload di file

Si vedano le definizioni delle funzioni is_uploaded_file() e move_uploaded_file() per maggiori dettagli. L'esempio seguente illustra il processamento di un file inviato tramite un form.

<?php
// Nelle versioni di PHP precedenti alla 4.1.0 si deve utilizzare  $HTTP_POST_FILES anzichè
// $_FILES.

$uploaddir '/var/www/uploads/';
$uploadfile $uploaddir basename($_FILES['userfile']['name']);

echo 
'<pre>';
if (
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    echo 
"File is valid, and was successfully uploaded.\n";
} else {
    echo 
"Possibile attacco tramite file upload!\n";
}

echo 
'Alcune informazioni di debug:';
print_r($_FILES);

print 
"</pre>";

?>

Lo script PHP che riceve il file caricato dovrebbe implementare la logica necessaria per determinare cosa deve essere fatto con il file caricato. E' possibile, per esempio, utilizzare la variabile $_FILES['userfile']['size'] per eliminare file che siano troppo grandi o troppo piccoli. È possibile utilizzare la variabile $_FILES['userfile']['type'] per eliminare tutti i file che non soddisfano certi criteri, ma si utilizzi questo metodo solo come il primo di una serie di controlli, poiché il valore è completamente sotto il controllo del client e non è controllato dal lato PHP. A partire da PHP 4.2.0, si può utilizzare $_FILES['userfile']['error'] ed organizzare la logica in base ai codici di errore. Quale che sia la logica, bisognerebbe comunque sempre cancellare il file dalla directory temporanea e spostarlo da qualche altra parte.

Se non si è selezionato alcun file per l'upload, il PHP restituirà $_FILES['userfile']['size'] a 0, e $_FILES['userfile']['tmp_name'] vuoto.

Il file sarà eliminato dalla directory temporanea al termine della richiesta se non è stato mosso e rinominato.

Example #3 Upload di una serie di file

Il PHP supporta le matrici HTML anche con i file.

<form action="" method="post" enctype="multipart/form-data">
<p>Pictures:
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="submit" value="Send" />
</p>
</form>
<?php
foreach ($_FILES["pictures"]["error"] as $key => $error) {
    if (
$error == UPLOAD_ERR_OK) {
        
$tmp_name $_FILES["pictures"]["tmp_name"][$key];
        
$name $_FILES["pictures"]["name"][$key];
        
move_uploaded_file($tmp_name"data/$name");
    }
}
?>

Un indicatore di progressione del caricamento può essere implementato usando Progressione di un caricamento in Sessione.

add a note add a note

User Contributed Notes 13 notes

up
240
Anonymous
1 year ago
For the love of god, don't do what michael suggests in http://php.net/manual/en/features.file-upload.post-method.php#94973 or you will be instantly pwned by someone uploading a php-shell to your script dir.

When the mods come to delete this note for violating the don't-refer-to-another-note rule, please please /please/ delete michael's note too.
up
7
mpyw
1 month ago
Do not use Coreywelch or Daevid's way, because their methods can handle only within two-dimensional structure. $_FILES can consist of any hierarchy, such as 3d or 4d structure.

The following example form breaks their codes:

<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="files[x][y][z]">
    <input type="submit">
</form>

As the solution, you should use PSR-7 based zendframework/zend-diactoros.

GitHub:

https://github.com/zendframework/zend-diactoros

Example:

<?php

use Psr\Http\Message\UploadedFileInterface;
use
Zend\Diactoros\ServerRequestFactory;

$request = ServerRequestFactory::fromGlobals();

if (
$request->getMethod() !== 'POST') {
   
http_response_code(405);
    exit(
'Use POST method.');
}

$uploaded_files = $request->getUploadedFiles();

if (
    !isset(
$uploaded_files['files']['x']['y']['z']) ||
    !
$uploaded_files['files']['x']['y']['z'] instanceof UploadedFileInterface
) {
   
http_response_code(400);
    exit(
'Invalid request body.');
}

$file = $uploaded_files['files']['x']['y']['z'];

if (
$file->getError() !== UPLOAD_ERR_OK) {
   
http_response_code(400);
    exit(
'File uploading failed.');
}

$file->moveTo('/path/to/new/file');

?>
up
12
coreywelch+phpnet at gmail dot com
6 months ago
The documentation doesn't have any details about how the HTML array feature formats the $_FILES array.

Example $_FILES array:

For single file -

Array
(
    [document] => Array
        (
            [name] => sample-file.doc
            [type] => application/msword
            [tmp_name] => /tmp/path/phpVGCDAJ
            [error] => 0
            [size] => 0
        )
)

Multi-files with HTML array feature -

Array
(
    [documents] => Array
        (
            [name] => Array
                (
                    [0] => sample-file.doc
                    [1] => sample-file.doc
                )

            [type] => Array
                (
                    [0] => application/msword
                    [1] => application/msword
                )

            [tmp_name] => Array
                (
                    [0] => /tmp/path/phpVGCDAJ
                    [1] => /tmp/path/phpVGCDAJ
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                )

            [size] => Array
                (
                    [0] => 0
                    [1] => 0
                )

        )

)

The problem occurs when you have a form that uses both single file and HTML array feature. The array isn't normalized and tends to make coding for it really sloppy. I have included a nice method to normalize the $_FILES array.

<?php

   
function normalize_files_array($files = []) {

       
$normalized_array = [];

        foreach(
$files as $index => $file) {

            if (!
is_array($file['name'])) {
               
$normalized_array[$index][] = $file;
                continue;
            }

            foreach(
$file['name'] as $idx => $name) {
               
$normalized_array[$index][$idx] = [
                   
'name' => $name,
                   
'type' => $file['type'][$idx],
                   
'tmp_name' => $file['tmp_name'][$idx],
                   
'error' => $file['error'][$idx],
                   
'size' => $file['size'][$idx]
                ];
            }

        }

        return
$normalized_array;

    }

?>

The following is the output from the above method.

Array
(
    [document] => Array
        (
            [0] => Array
                (
                [name] => sample-file.doc
                    [type] => application/msword
                    [tmp_name] => /tmp/path/phpVGCDAJ
                    [error] => 0
                    [size] => 0
                )

        )

    [documents] => Array
        (
            [0] => Array
                (
                    [name] => sample-file.doc
                    [type] => application/msword
                    [tmp_name] => /tmp/path/phpVGCDAJ
                    [error] => 0
                    [size] => 0
                )

            [1] => Array
                (
                    [name] => sample-file.doc
                    [type] => application/msword
                    [tmp_name] => /tmp/path/phpVGCDAJ
                    [error] => 0
                    [size] => 0
                )

        )

)
up
46
daevid at daevid dot com
7 years ago
I think the way an array of attachments works is kind of cumbersome. Usually the PHP guys are right on the money, but this is just counter-intuitive. It should have been more like:

Array
(
    [0] => Array
        (
            [name] => facepalm.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpn3FmFr
            [error] => 0
            [size] => 15476
        )

    [1] => Array
        (
            [name] =>
            [type] =>
            [tmp_name] =>
            [error] => 4
            [size] =>
        )
)

and not this
Array
(
    [name] => Array
        (
            [0] => facepalm.jpg
            [1] =>
        )

    [type] => Array
        (
            [0] => image/jpeg
            [1] =>
        )

    [tmp_name] => Array
        (
            [0] => /tmp/phpn3FmFr
            [1] =>
        )

    [error] => Array
        (
            [0] => 0
            [1] => 4
        )

    [size] => Array
        (
            [0] => 15476
            [1] => 0
        )
)

Anyways, here is a fuller example than the sparce one in the documentation above:

<?php
foreach ($_FILES["attachment"]["error"] as $key => $error)
{
      
$tmp_name = $_FILES["attachment"]["tmp_name"][$key];
       if (!
$tmp_name) continue;

      
$name = basename($_FILES["attachment"]["name"][$key]);

    if (
$error == UPLOAD_ERR_OK)
    {
        if (
move_uploaded_file($tmp_name, "/tmp/".$name) )
           
$uploaded_array[] .= "Uploaded file '".$name."'.<br/>\n";
        else
           
$errormsg .= "Could not move uploaded file '".$tmp_name."' to '".$name."'<br/>\n";
    }
    else
$errormsg .= "Upload error. [".$error."] on file '".$name."'<br/>\n";
}
?>
up
15
eslindsey at gmail dot com
7 years ago
Also note that since MAX_FILE_SIZE hidden field is supplied by the browser doing the submitting, it is easily overridden from the clients' side.  You should always perform your own examination and error checking of the file after it reaches you, instead of relying on information submitted by the client.  This includes checks for file size (always check the length of the actual data versus the reported file size) as well as file type (the MIME type submitted by the browser can be inaccurate at best, and intentionally set to an incorrect value at worst).
up
4
anon
1 year ago
For clarity; the reason you would NOT want to replace the example script with
$uploaddir = './';
is because if you have no coded file constraints a nerd could upload a php script with the same name of one of your scripts in the scripts directory.

Given the right settings and permissions php-cgi is capable of replacing even php files.

Imagine if it replaced the upload post processor file itself. The next "upload" could lead to some easy exploits.

Even when replacements are not possible; uploading an .htaccess file could cause some problems, especially if it is sent after the nerd throws in a devious script to use htaccess to redirect to his upload.

There are probably more ways of exploiting it. Don't let the nerds get you.

More sensible to use a fresh directory for uploads with some form of unique naming algorithm; maybe even a cron job for sanitizing the directory so older files do not linger for too long.
up
3
Mark
5 years ago
$_FILES will be empty if a user attempts to upload a file greater than post_max_size in your php.ini

post_max_size should be >= upload_max_filesize in your php.ini.
up
1
Age Bosma
5 years ago
"If no file is selected for upload in your form, PHP will return $_FILES['userfile']['size'] as 0, and $_FILES['userfile']['tmp_name'] as none."

Note that the situation above is the same when a file exceeding the MAX_FILE_SIZE hidden field is being uploaded. In this case $_FILES['userfile']['size'] is also set to 0, and $_FILES['userfile']['tmp_name'] is also empty. The difference would only be the error code.
Simply checking for these two conditions and assuming no file upload has been attempted is incorrect.

Instead, check if $_FILES['userfile']['name'] is set or not. If it is, a file upload has at least been attempted (a failed attempt or not). If it is not set, no attempt has been made.
up
1
claude dot pache at gmail dot com
7 years ago
Note that the MAX_FILE_SIZE hidden field is only used by the PHP script which receives the request, as an instruction to reject files larger than the given bound. This field has no significance for the browser, it does not provide a client-side check of the file-size, and it has nothing to do with web standards or browser features.
up
-6
milosdj at gmail dot com
1 year ago
MAX_FILE_SIZE is completely useless from browser perspective. Up to this day there is no browser that supports it.

From PHP's point there is one mention to max_file_size in source code.
http://stackoverflow.com/a/6273418/352260

In essence, no need to use MAX_FILE_SIZE until browsers start to support it. Which will probably never happen.
But if html5 kicks in, input maxlength attribute might become applicable to file input type.
up
-18
Anonymous
3 years ago
Normalizing $_FILES structure:

<?php
    $files
= [];
   
$fix = function (&$files, $values, $prop) use (&$fix) {
        foreach (
$values as $key => $value) {
            if (
is_array($value)) {
               
$fix($files[$key], $value, $prop);
            } else {
               
$files[$key][$prop] = $value;
            }
        }
    };
    foreach (
$_FILES as $name => $props) {
        foreach (
$props as $prop => $value) {
            if (
is_array($value)) {
               
$fix($files[$name], $value, $prop);
            } else {
               
$files[$name][$prop] = $value;
            }
        }
    }
?>
up
-10
bimal at sanjaal dot com
1 year ago
Some suggestions here:

1. It is always better to check for your error status. If MAX_FILE_SIZE is active and the uploaded file crossed the limit, it will set the error. So, only when error is zero (0), move the file.

2. If possible, never allow your script to upload in the path where file can be downloaded. Point your upload path to outside of public_html area or prevent direct browsing (using .htaccess restrictions). Think, if someone uploads malicious code, specially php codes, they will be executed on the server.

3. Do not use the file name sent by the client. Regenerate a new name for newly uploaded file. This prevents overwriting your old files.

4. Regularly track the disk space consumed, if you are running out of storage.
up
-19
mail at markuszeller dot com
5 years ago
If you want to increase the upload size, it could make sense to allow it only to a specified directory and not in the php.ini for the whole domain or server. In my case it worked very well placing that into the .htaccess file like this:

php_value    upload_max_filesize    100M
php_value    post_max_size    101M

Remember, post_max_size must be bigger than the upload_max_filesize.
To Top