ScotlandPHP 2016

Dateiuploads mit POST

Dieses Feature erlaubt es Usern sowohl Text- als auch Binärdaten hochzuladen. Mit PHP's Authentifizierungs- und Dateifunktionen besteht volle Kontrolle darüber, wer Dateien hochladen darf und was mit den Dateien geschehen soll, wenn der Upload beendet ist.

PHP kann Dateiuploads von jedem RFC-1867 konformen Browser empfangen.

Hinweis: Diesbezügliche Konfigurationshinweise

Siehe auch die Anweisungen file_uploads, upload_max_filesize, upload_tmp_dir, post_max_size und max_input_time in der php.ini

PHP unterstützt auch Dateiuploads nach der PUT-Methode, die beispielsweise vom Netscape Composer und den W3C Amaya Clients benutzt wird. Siehe dazu PUT-Unterstützung für nähere Informationen.

Beispiel #1 Formular zum Hochladen von Dateien

Um Dateieuploads zu ermöglichen muss ein Formular erstellt werden, welches ungefähr wie folgt aussieht:

<!-- Die Encoding-Art enctype MUSS wie dargestellt angegeben werden -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
    <!-- MAX_FILE_SIZE muss vor dem Dateiupload Input Feld stehen -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    <!-- Der Name des Input Felds bestimmt den Namen im $_FILES Array -->
    Diese Datei hochladen: <input name="userfile" type="file" />
    <input type="submit" value="Send File" />
</form>

__URL__ im obigen Beispiel sollte durch die URL eines PHP Scripts ersetzt werden.

Das "hidden" Feld MAX_FILE_SIZE muss dem "file" Eingabefeld vorangestellt werden; der angegebene Wert bestimmt die maximale akzeptierte Dateigröße in Bytes. Dieses Formular-Element sollte immer benutzt werden, da es dem Benutzer erspart auf einen großen Dateiupload zu warten, nur um festzustellen, dass die Datei zu groß war und der Upload fehlgeschlagen ist. Es sollte jedoch beachtet werden, dass diese Browser-seitige Einstellung sehr einfach zu umgehen ist und man sich daher niemals darauf verlassen sollte, dass sie übergroße Dateiuploads verhindert. Die Server-seitige Option for die Maximalgröße kann hingegen nicht umgangen werden.

Hinweis:

Das Hochladeformular muss unbedingt das Attribut enctype="multipart/form-data" angeben, andernfalls wird der Upload nicht funktionieren.

Die globale Variable $_FILES enthält alle Informationen über hochgeladene Dateien.

Im Folgenden sind die Inhalte von $_FILES für das obige Beispielformular aufgelistet. Beachten Sie, dass dies auf der Annahme basiert, dass der Name des Dateiuploads wie in dem obigen Beispielskript userfile ist. Es kann aber auch jeder andere Name genutzt werden.

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

Der ursprüngliche Dateiname auf dem Computer des Benutzers.

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

Der Mime-Type der Datei, falls der Browser diese Information zur Verfügung gestellt hat. Ein Beispiel wäre "image/gif". Dieser Mime-Type wird jedoch nicht von PHP geprüft und kann somit falsch sein.

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

Die Größe der hochgeladenen Datei in Bytes.

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

Der temporäre Dateiname, unter dem die hochgeladene Datei auf dem Server gespeichert wurde.

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

Der Fehlercode des Uploads.

Standardmäßig werden Dateien in dem standardmäßigen temporären Verzeichnis des Servers gespeichert, außer es wurde mittels upload_tmp_dir in der php.ini ein anderer Ort konfiguriert. Das Standardverzeichnis des Servers kann durch das Setzen der Umgebungsvariablen TMPDIR in der Umgebung, in der PHP ausgeführt wird, geändert werden. Das Setzen mittels der Funktion putenv() innerhalb eines Skriptes ist nicht möglich. Mittels dieser Umgebungsvariable kann auch sichergestellt werden, dass auch andere Operationen an hochgeladenen Dateien arbeiten können.

Beispiel #2 Dateiuploads prüfen

Weitere Informationen finden Sie auch in den Beschreibungen für is_uploaded_file() und move_uploaded_file(). Das folgende Beispiel verarbeitet einen von einem HTML-Formular kommenden Dateiupload.

<?php 
// In PHP kleiner als 4.1.0 sollten Sie $HTTP_POST_FILES anstatt 
// $_FILES verwenden.

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

echo 
'<pre>';
if (
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    echo 
"Datei ist valide und wurde erfolgreich hochgeladen.\n";
} else {
    echo 
"Möglicherweise eine Dateiupload-Attacke!\n";
}

echo 
'Weitere Debugging Informationen:';
print_r($_FILES);

print 
"</pre>";

?>

Das die hochgeladene Datei empfangende Skript sollte die notwendige Logik zur Entscheidung enthalten, was mit der hochgeladenen Datei geschehen soll. Sie können zum Beispiel $_FILES['userfile']['size'] benutzen, um zu kleine bzw. zu große Dateien wegzuwerfen. Sie können $_FILES['userfile']['type'] nutzen, um Dateien eines unerwünschten Typs wegzuwerfen, sollten dabei jedoch beachten, dass dieser Wert vollständig vom Benutzer kontrolliert wird und somit falsch sein kann. Sie können Ihre Logik auch mittels $_FILES['userfile']['error'] anhand der Fehlercodes planen. Egal welche Logik Sie verwenden, Sie sollten die Datei in dem temporären Verzeichnis entweder löschen, oder an einen anderen Ort verschieben.

Wenn im Formular keine Datei ausgewählt wurde so wird $_FILES['userfile']['size'] von PHP auf 0 gesetzt und $_FILES['userfile']['tmp_name'] ist leer.

Wurde die Datei in dem temporären Verzeichnis nicht verschoben oder umbenannt, wird sie am Ende des Requests gelöscht.

Beispiel #3 Hochladen eines Arrays von Dateien

PHP unterstützt HTML Arrays auch für Dateien.

<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");
    }
}
?>

Eine Fortschrittsanzeige für Datei Uploads kann mittels Session Upload Progress implementiert werden.

add a note add a note

User Contributed Notes 14 notes

up
239
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
8
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
44
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
9
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
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
1
russianhacker
6 days ago
function normalize_files_array($files = []) {

    $arr = [];

    foreach ($files['name'] as $index => $filename) {
        $arr[] = array(
            'name' => $filename,
            'tmp_name' => $files['tmp_name'][$index],
            'error' => $files['error'][$index],
            'size' => $files['size'][$index],
            'type' => $files['type'][$index]
        );
    }

    return $arr;
}
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
3
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
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
-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
-11
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