Es gibt viele Projekte bei denen man dem Benutzer die Möglichkeit geben möchte selbst Dateien auf den Server hochzuladen, beispielsweise bei Bilderhostern, oder Foren, bei denen der Benutzer die Möglichkeit hat Dateien an einen Beitrag anzuhängen. Sowas lässt sich mit PHP problemlos realisieren.
In diesem Artikel beschreibe ich, wie man ein Formular zum Hochladen von Dateien erstellt und wie man die Dateien serverseitig mit PHP behandelt. Außerdem erkläre ich wie mehrere Dateien auf einmal hochgeladen werden können.
Das Dateiformular
Zuerst einmal wird ein Formular benötigt, in dem der Benutzer eine Datei auswählen kann. Ein input-Element vom Typ file erstellt ein Eingabefeld und eine Durchsuchen-Schaltfläche, mittels derer der Benutzer eine Datei auswählen kann. Das versteckte input-Element mit dem Namen MAX_FILE_SIZE ist ein Hinweis für den Browser des Benutzers, value gibt in Bytes an, wie groß eine hochgeladene Datei maximal sein darf. Die Größe der Datei muss natürlich serverseitig noch einmal überprüft werden, da manche Browser die Angabe evtl. nicht beachten, und weil sie leicht umgangen werden kann. PHP überprüft zwar selbst, ob die Datei größer als MAX_FILE_SIZE ist, aber da diese Angabe leicht manipuliert werden kann, bietet diese Überprüfung keine Sicherheit.
<form action="uploaded.php" enctype="multipart/form-data" method="post"> <fieldset> <legend>Datei hochladen</legend> <label for="file">Bitte eine Datei auswählen.</label><br /> <!-- Hinweis fuer den Browser des Benutzers, maximale Dateigroesse --> <input type="hidden" name="MAX_FILE_SIZE" value="100000" /> <!-- Eingabefeld+Durchsuchen-Schaltflaeche --> <input type="file" id="file" name="beispieldatei" /> <!-- Schaltflaeche zum Absenden der Datei --> <input type="submit" value="Hochladen" /> </fieldset> </form>
Die action-Eigenschaft des form-Elements gibt an, an welche Adresse/Datei die Daten übertragen werden sollen. Mit method wird angegeben, wie die Daten übertragen werden sollen, beim POST-Verfahren sendet der Browser eine spezielle POST-Anfrage mit den Daten an den Server. Die Eigenschaft enctype gibt die Kodierung der übertragenen Daten an, hier muss man multipart/form-data verwenden.
Die hochgeladene Datei verarbeiten
Wenn der Benutzer das Formular absendet wird die unter action angegebene Seite aufgerufen, dabei wird die Datei vom Browser an den Server gesendet. Die Datei wird vom Server in einem temporären Verzeichnis gespeichert. Im PHP-Skript muss diese nun verarbeitet und an ihren Bestimmungsort verschoben werden.
PHP sammelt die wichtigsten Informationen über die Datei im autoglobalen Feld $_FILES. Für jede hochgeladene Datei wird ein Element mit dem gleichen Namen wie das input-Element des Formulars (im Beispiel: beispieldatei) in diesem Feld angelegt. Auch dieses Element ist wieder ein Feld, mit folgenden Elementen:
| Element | Inhalt |
|---|---|
| name | Der Name der Datei auf dem Rechner des Benutzers |
| type | MIME-Typ der Datei, z.B. “image/png” |
| tmp_name | Der Pfad und Name, unter der die Datei gespeichert wurde (im temporären Verzeichnis), beispielsweise “/tmp/phpKnTtNy”. |
| error | Der Fehlercode, mittels diesem kann überprüft werden ob beim Hochladen ein Fehler aufgetreten ist. |
| size | Die Größe der Datei, in Bytes. |
Anhand dieser Daten kann die Datei erstmal validiert werden, so können Dateien die zu groß oder zu klein sind, den falschen Dateityp/Endung haben, aussortiert werden. Dateien mit der Endung .php sollten auf jeden Fall aussortiert bzw. wenigstens umbenannt werden, sonst können nämlich PHP-Dateien hochgeladen werden die auch vom PHP-Interpreter ausgeführt werden würden!
Verschieben der Datei
Um die Datei nun weiter zu bearbeiten bzw. sie zu speichern, muss sie vom temporären Verzeichnis in das gewünschte Zielverzeichnis verschoben werden. Nach der Ausführung des Skripts wird die Datei gelöscht, falls sie sich noch im temporären Verzeichnis befindet. Um eine Datei nicht zu speichern, genügt es also, sie einfach nicht aus dem temporären Verzeichnis rauszuholen.
$zielverzeichnis = './hochgeladen/'; if ($_FILES['beispieldatei']['error'] != UPLOAD_ERR_OK) die('Ein Fehler ist aufgetreten.'); if (move_uploaded_file($_FILES['beispieldatei']['tmp_name'], $zielverzeichnis . basename($_FILES['beispieldatei']['name']))) { echo "Datei wurde hochgeladen!"; } else { echo "Ein Fehler ist aufgetreten!"; }
Zuerst wird die error-Variable überprüft, wenn ein Fehler aufgetreten ist, bricht das Skript ab. Anschließend wird die Datei mittels move_uploaded_file($datei, $ziel) in das Zielverzeichnis verschoben. Die Funktion überprüft ob es sich bei der übergebenen Datei um eine hochgeladene Datei handelt, ist das nicht der Fall, wird sie nicht verschoben und es wird false zurückgegeben. Konnte die Datei aus irgendeinem Grund nicht verschoben werden, wird ebenfalls false zurückgegeben, sowie eine Warnung ausgegeben. Die Zieldatei wird überschrieben, wenn sie bereits existiert.
Wozu der basename-Aufruf?
Das Beispielskript speichert die Datei unter dem selben Namen, den sie auch auf dem Rechner des Benutzers hat. Die basename($pfad)-Funktion extrahiert den Dateinamen einer Datei aus einer Pfadangabe.
echo basename("/etc/passwd"); // Ausgabe: passwd
Durch den basename-Aufruf wird sichergestellt, das es sich bei dem Dateinamen wirklich um einen Dateinamen und nicht um einen Pfad handelt. Ohne diese Überprüfung könnten böswillige Benutzer möglicherweise dafür sorgen das ihre Dateien in anderen Verzeichnissen als im Zielverzeichnis landen (Sicherheitslücke!).
Die Datei validieren
Um zu verhindern, das Benutzer Dateien hochladen, die dem Server oder anderen Benutzern schaden könnten , oder aus anderen Gründen unerwünscht sind, empfiehlt es sich eine hochgeladene Datei erst zu überprüfen, bevor sie verschoben wird.
- Die Dateigröße. Je nachdem wie die hochgeladenen Dateien verwendet werden, sollte man überprüfen, das der Benutzer keine unsinnig großen (oder kleinen) Dateien hochlädt. Der Speicherplatz auf dem Server ist schließlich begrenzt. Die Variable
$_FILES[...]['size']kann hierzu verwendet werden. - Die Dateiendung bzw. der MIME-Typ. Oft sind nur bestimmte Arten von Dateien erwünscht, beispielsweise Bilder bei einem Bilderhoster. Unpassende Dateitypen können aussortiert werden. Dateien mit der Endung
.phpbzw..php3/.php4/.php5/usw. sollten ebenfalls aussortiert werden, bzw. die Dateiendung sollte geändert werden (beispielsweise zu .phps), ansonsten können die Benutzer ausführbare PHP-Dateien hochladen, was in den meisten Fällen unerwünscht sein dürfte (Sicherheitslücke). Hierzu können die Variablen$_FILES[...]['type'](MIME-Typ) und$_FILES[...]['name'](Dateiname) verwendet werden. Mit folgender Funktion lässt sich die Dateiendung aus dem Dateinamen ermitteln:function endungErmitteln($dateiname) { if (false !== ($punkt = strrpos($dateiname, '.'))) { if (strlen($dateiname) > $punkt) return substr($dateiname, $punkt + 1); } // Keine Dateiendung return false; }
Es ist noch zu beachten, das es Dateien ohne Endung gibt, und das Dateiendungen in allen möglichen Formen von Groß- und Kleinschreibung vorkommen können (Bsp. Bild.JpEg).
- Der Dateiname. Der Name der Datei kann auf nicht erwünschte Zeichen überprüft werden, außerdem könnte man überprüfen ob der Dateiname nicht zu lang ist.
- Viren und andere Schädlinge. Ein Benutzer könnte (un-)absichtlich eine Datei hochladen die einen Virus bzw. einen Wurm o.Ä. enthält. Es gibt Erweiterungen für PHP, die es einem ermöglichen eine Datei auf solche Schadprogramme zu überprüfen.
Wie man hochgeladene Dateien automatisch auf Viren überprüft mit php-clamavlib
Mögliche Fehler
Beim Hochladen von Dateien können auch Fehler auftreten, deshalb enthält das $_FILES-Feld im Dateielement auch ein Element namens error, welches angibt ob ein Fehler aufgetreten ist, bzw. um welchen Fehler es sich handelt.
Mögliche Fehlercodes in $_FILES[...]['error']
Mit folgenden Fehlercodes kann der error-Wert verglichen werden, um herauszufinden, was für ein Fehler aufgetreten ist.
| Zahl | Fehlercode | Bedeutung |
|---|---|---|
| 0 | UPLOAD_ERR_OK | Kein Fehler, die Datei wurde erfolgreich hochgeladen. |
| 1 | UPLOAD_ERR_INI_SIZE | Die Datei ist größer als upload_max_filesize, einer Option in der Konfigurationsdatei von PHP. |
| 2 | UPLOAD_ERR_FORM_SIZE | Die Datei ist größer, als im Formular mittels MAX_FILE_SIZE angegeben. |
| 3 | UPLOAD_ERR_PARTIAL | Die Datei wurde nur teilweise hochgeladen. |
| 4 | UPLOAD_ERR_NO_FILE | Es wurde keine Datei hochgeladen. |
| 6 | UPLOAD_ERR_NO_TMP_DIR | Es gibt kein temporäres Verzeichnis, unter dem die Datei abgelegt werden könnte. Erst ab PHP 5.0.3 |
| 7 | UPLOAD_ERR_CANT_WRITE | Die Datei konnte nicht erstellt werden, bzw. konnte nicht auf das Speichermedium (Festplatte) geschrieben werden. Erst ab PHP 5.1.0 |
| 8 | UPLOAD_ERR_EXTENSION | Der Hochladevorgang wurde durch eine PHP-Erweiterung gestoppt. Erst ab PHP 5.2.0 |
Die Überprüfung auf Fehler könnte beispielsweise so aussehen:
$fehlercode = $_FILES['beispieldatei']['error']; if ($fehlercode != UPLOAD_ERR_OK) { echo '<h2>Beim Hochladen ist ein Fehler aufgetreten!</h2>'; switch($fehlercode) { case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: echo 'Die Datei ist zu groß.'; break; case UPLOAD_ERR_PARTIAL: echo 'Die Datei wurde nur teilweise hochgeladen.'; break; case UPLOAD_ERR_NO_FILE: echo 'Es wurde keine Datei hochgeladen.'; break; default: echo 'Ein interner Fehler ist aufgetreten.'; echo 'Eine E-Mail wurde an den Administrator geschickt.'; echo 'Bitte versuchen Sie es zu einem späteren Zeitpunkt nochmal.'; // E-Mail mit dem Fehlercode an den Admin schicken [...] ;-) break; } }
Optionen in der php.ini
Die Option upload_max_filesize gibt an, wie groß die Dateien die hochgeladen werden, zusammen maximal sein dürfen. Es wird also beim Hochladen mehrerer Dateien nicht überprüft wie groß die einzelnen Dateien sind, sondern wie groß sie alle zusammen sind. Die Option post_max_size muss ebenfalls beachtet werden, da die Dateien über das POST-Verfahren hochgeladen werden, limitiert auch diese Option die Gesamtgröße der Dateien die hochgeladen werden. Es empfiehlt sich den Wert dieser Option etwas höher einzustellen, da noch andere Formulardaten übertragen werden.
Außerdem gibt es noch die Option file_uploads, ist diese auf off eingestellt, ist es gar nicht möglich, Dateien hochzuladen.
Zusätzlich muss auch die max_input_time beachtet werden, welche angibt wie lange PHP für das Auswerten von POST/GET/REQUEST-Daten brauchen darf. Da das Hochladen von Dateien durchaus lange dauern kann, beispielsweise wenn die Datei sehr groß ist, oder wenn der Benutzer eine langsame Verbindung hat (ISDN, langsame DSL-Verbindungen).
; Ist das Hochladen von Dateien erlaubt? file_uploads = On ; Maximale Gesamtgroesse der Dateien die hochgeladen werden ; K = Kilobyte, M = Megabyte, G = Gigabyte. upload_max_filesize = 100M ; Maximale Groesse mittels des POST-Verfahrens uebermittelter Daten post_max_size = 102M ; Wie lange darf PHP fuer die Auswertung von uebermittelten Daten brauchen? max_input_time = 3600 ; 1 Stunde
Wenn die Dateien noch weiter verarbeitet werden, also beispielsweise Vorschaubilder erstellt werden o.Ä. muss eventuell auch noch die max_execution_time beachtet werden. Diese gibt an, wie lange PHP-Skripte rechnen dürfen bzw. wie viel Prozessorzeit sie verbrauchen dürfen bevor sie beendet werden. Die Zeit die für Systemaufrufe, Datenbankabfragen o.Ä. aufgewendet wird, fließt nicht in die Berechnung mit ein, sondern nur die Zeit in der das PHP-Skript selbst ausgeführt wird.
Mehrere Dateien auf einmal hochladen
Es ist auch problemlos möglich, mehrere Dateien auf einmal hochzuladen. Dazu fügt man im Formular einfach weitere input-Elemente vom Typ file hinzu. Eine kleine Besonderheit gibt es allerdings zu beachten: Wenn man den Elementen einfach unterschiedliche Namen gibt, werden für jedes Element auch Elemente im $_FILES-Feld erstellt. Wenn man alle Elemente gleich benennt, muss hinter dem Namen [] stehen, das sieht dann z.B. so aus:
<input type="file" id="file" name="datei[]" /> <input type="file" id="file" name="datei[]" />
In diesem Fall würde PHP nur ein Element im $_FILES-Feld erstellen, die Unterelemente davon (name, size, type, etc.) hätten dann aber mehrere Werte/Elemente.




Meiner Erfahrung nach ist es sehr beunruhigend, mehrere Dateien hochzuladen und nur auf den “Spinner” des Browsers zu starren. Also irgendein sich bewegendes Bildchen, dass die Zweifel nicht wirklich ausräumt irgendetwas, etwa das Hochladen der Dateien, geschehe.
Deshalb ist es im Sinne der “Usability” ratsam in den sauren Apfel zu beissen und den Datei-Upload mittels Flash zu realisieren – und zwar mit Ladebalken. Das macht WordPress schön vor.
Denn: mit JavaScript kann man keine Dateien hochladen, aber genau nach solchen Lösungen zu suchen googeln viele Programmierer (stundenlang und vergeblich) am Anfang.
Allerdings bin ich mir jetzt nicht sicher, ob PHP nicht beim MIME-Typ der hochgeladenen Daten einfach auf den Browser des Hochladers vertraut. Besser ist es also, PECL::fileinfo zu benutzen, dass wie der “file” Befehl unter Linux von der Datei auf den Inhalt schließt.
Programmierer entwickeln und testen Anwendungen oft auf ein und demselben Rechner. So selbst große Dateien hochzuladen funktioniert innerhalb weniger (Milli-)Sekunden.
Aber wenn nun ein ISDN oder Benutzer einer langsamen DSL Leitung eine dann nicht mehr so kleine Datei hochladen möchte, schlagen die Timeouts von PHP zu, derer es zwei gibt. Also wichtig, nicht vergessen auch auf diese Werte zu achten und ggf. letzteren zu erhöhen:
max_execution_time = 30 ; Maximum execution time of each script, in seconds
max_input_time = 60 ; Maximum amount of time each script may spend parsing request data
Vielen Dank für deine beiden Kommentare!
Über das Hochladen von Dateien mittels Flash bzw. darüber wie man einen Fortschrittsbalken realisieren kann, schreibe ich noch einen Artikel. Ist zumindest geplant. PECL::fileinfo muss ich mir mal ansehen, danke für den Hinweis.
Die erste Option ist für das Hochladen von Dateien nur bedingt relevant. Die max_execution_time limitiert nur die Prozessorzeit des PHP-Skriptes, nicht die gesamte Ausführzeit oder die Zeit in der auf Daten gewartet wird. Das kann man auch mit folgendem Code testen:
Wenn die hochgeladenen Daten noch bearbeitet werden (z.B. Vorschaubilder erstellen) muss man natürlich auch diesen Wert beachten, da hast du Recht.
Die max_input_time kannte ich gar nicht, ich habe das mal hinzugefügt, vielen Dank für den Hinweis.