decocode decocode deco    

Upload/Download #

Uploads #

Dateien können über ein spezielles HTML-Formular hochgeladen werden. Die Datei für dieses Beispiel hat hier den Namen upload.php.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<style type='text/css' media='screen, print'>
  td { border:1px solid #ccc; }
</style>
<?php
  echo "<form action='upload.php' method='post' enctype='multipart/form-data'><div>";

  echo "<input type='file' name='file1'> ";
  echo "<input type='submit' value='Upload'>";
  echo "</div></form>";
  $upload_dir = "uploads/";
  if ($_FILES['file1']['name']) {
    $local_file = stripslashes($_FILES['file1']['name']);
    $temp_file = $_FILES['file1']['tmp_name'];
    $remote_file = $upload_dir.$local_file;
    echo "<div>";
    while ($value = each($_FILES['file1'])) echo $value[0].": ".stripslashes($value[1])."<br>";
    echo "</div>";
    if (move_uploaded_file($temp_file, $remote_file))
    echo "<p><b><span style='color:green;'>Der Upload der Datei <samp>".$local_file."</samp> war erfolgreich!</span></b></p>";
    else echo "<p><b><span style='color:red;'>Der Upload der Datei <samp>".$local_file."</samp> ist gescheitert!</span></b></p>";
  }
  echo "<hr><h2>Inhalt des Verzeichnisses <samp>".$upload_dir."</samp></h2>";
  if (file_exists($upload_dir)) {
    if ($dh = opendir($upload_dir)) {
      echo "<table><tr><td>Datei</td><td>Größe</td><td>Upload</td></tr>";
      while ($file = readdir($dh)) {
        if (filetype($upload_dir.$file) == "file") {
          echo "<tr><td><samp><a href='".$upload_dir.$file."'>".$file."</a></samp></td>";
          echo "<td style='text-align:right;'><samp>".number_format(filesize($upload_dir.$file),0,"."," ")." Bytes</samp></td>";
          echo "<td><samp>".date("d.m.Y - H:i \h", filemtime($upload_dir.$file))."</samp></td></tr>";
          $count++;
        }
      }
      echo "</table>";
      closedir($dh);
    }
    if (!$count) echo "<p><b>Das Verzeichnis <samp>".$upload_dir."</samp> enthält keine Dateien!</b></p>";
    if (!is_writable($upload_dir)) echo "<p><b><span style='color:red;'>Für das Verzeichnis <samp>".$upload_dir."</samp> bestehen keine Schreibrechte!</span></b></p>";
  } else echo "<p><b><span style='color:red;'>Das Verzeichnis <samp>".$upload_dir."</samp> existiert nicht!</span></b></p>";
?>

Dieses Skript stellt ein Formular zur Verfügung, über das eine Datei in ein bestimmtes serverseitiges Verzeichnis (Zeile 10) hochgeladen werden kann. Weiterhin werden die internen Daten der Variable $_FILES angezeigt (Zeile 16) sowie die Inhalte des Upload-Verzeichnisses (ab Zeile 22). Es wird geprüft, ob das Upload-Verzeichnis existiert (Zeile 23) und dafür Schreibrechte existieren (Zeile 36). Sollte dies nicht der Fall sein, müssen diese in der Regel manuell zugewiesen werden.

Das <form>-Tag benötigt das Attribut enctype='multipart/form-data' für die Übermittlung des Daten-Streams an den Server. Weiterhin ist das Formularobjekt <input type='file' name='file1'> notwendig, um die eigene Festplatte nach einer Datei für den Upload durchsuchen zu können. Es können theoretisch mehrere Dateien gleichzeitig hochgeladen werden. Dazu würde man mehrere solcher Formularobjekte mit unterschiedlichen Namen einbinden.

Das Abschicken des Formulars wird in Zeile 11 mit if ($_FILES['file1']['name']) überprüft. Die gewählte Datei wird dann über move_uploaded_file($temp_file, $remote_file) in Zeile 18 in das Upload-Verzeichnis kopiert. Die Werte der Variablen $temp_file und $remote_file werden in den Zeilen 1214 zugewiesen.

Berücksichtigung von Sonderzeichen #

Nun müssen wir noch die unterschiedliche Toleranz gegenüber Sonderzeichen auf Linux- und Windows-Servern berücksichtigen:

1.: Während unter Windows Dateinamen die Sonderzeichen ?:|<>/*\" nicht enthalten dürfen, dürfen Dateinamen unter Linux (und vermutlich allen anderen Unixen) lediglich kein Slash / enthalten. Es kann nun passieren, dass das Webprojekt auf einem Windows-Server läuft und eine Datei von einem Linux-System hochgeladen wird, die eines oder mehrere der nicht tolerierten Sonderzeichen im Namen enthält. Damit es dadurch nicht zu Problemen kommt, müssen diese Zeichen ersetzt werden, bevor die Datei auf den Server kopiert wird.

2.: Unter Linux haben die gängigen Browser teilweise Probleme mit bestimmten Sonderzeichen, die wie gesagt auf Windows-Systemen ohnehin nicht in Dateinamen verwendet werden dürfen. Gegenwärtig sind das folgende:

Firefox (33): \
Chrome (31): \ "
Opera (12.16): \ " ? | : *

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<style type='text/css' media='screen, print'>
  td { border:1px solid #ccc; }
</style>
<?php
  echo "<form action='upload.php' method='post' enctype='multipart/form-data'><div>";

  echo "<input type='file' name='file1'> ";
  echo "<input type='submit' value='Upload'>";
  echo "</div></form>";
  $upload_dir = "uploads/";
  if ($_FILES['file1']['name']) {
    $local_file = stripslashes($_FILES['file1']['name']);
    $temp_file = $_FILES['file1']['tmp_name'];
    if (PHP_OS != "Linux") $local_file2 = strtr($local_file, "?:|<>/*\\\"", "_________"); else $local_file2 = $local_file;
    $remote_file = $upload_dir.$local_file2;
    echo "<div>";
    while ($value = each($_FILES['file1'])) echo $value[0].": ".stripslashes($value[1])."<br>";
    echo "</div>";
    if (move_uploaded_file($temp_file, $remote_file))
    echo "<p><b><span style='color:green;'>Der Upload der Datei <samp>".$local_file."</samp> war erfolgreich!</span></b></p>";
    else echo "<p><b><span style='color:red;'>Der Upload der Datei <samp>".$local_file."</samp> ist gescheitert!</span></b></p>";
    if ($local_file != $local_file2) echo "<p><b><span style='color:red;'>Die Datei <samp>".$local_file."</samp> enthielt unerlaubte Sonderzeichen und wurde nach <samp>".$local_file2."</samp> umbenannt!</span></b></p>";
  }
  echo "<hr><h2>Inhalt des Verzeichnisses <samp>".$upload_dir."</samp></h2>";
  if (file_exists($upload_dir)) {
    if ($dh = opendir($upload_dir)) {
      echo "<table><tr><td>Datei</td><td>Größe</td><td>Upload</td></tr>";
      while ($file = readdir($dh)) {
        if (filetype($upload_dir.$file) == "file") {
          echo "<tr><td><samp><a href='download.php?file=".rawurlencode($file)."'>".$file."</a></samp></td>";
          echo "<td style='text-align:right;'><samp>".number_format(filesize($upload_dir.$file),0,"."," ")." Bytes</samp></td>";
          echo "<td><samp>".date("d.m.Y - H:i \h", filemtime($upload_dir.$file))."</samp></td></tr>";
          $count++;
        }
      }
      echo "</table>";
      closedir($dh);
    }
    if (!$count) echo "<p><b>Das Verzeichnis <samp>".$upload_dir."</samp> enthält keine Dateien!</b></p>";
    if (!is_writable($upload_dir)) echo "<p><b><span style='color:red;'>Für das Verzeichnis <samp>".$upload_dir."</samp> bestehen keine Schreibrechte!</span></b></p>";
  } else echo "<p><b><span style='color:red;'>Das Verzeichnis <samp>".$upload_dir."</samp> existiert nicht!</span></b></p>";
?>

Zur Lösung von Problem 1 wird nun die Zeile 14 eingefügt, in der überprüft wird, ob es sich um einen Linux-Server handelt. Wenn nicht, werden die unzulässigen Zeichen durch einen Unterstrich _ ersetzt. Zusätzlich wird in diesem Fall eine Hinweismeldung ausgegeben (Zeile 22). Beachte die Änderung in Zeile 15!

Die Lösung für Problem 2 wird man möglicherweise mit JavaScript ausführen müssen (Überprüfung nach problematischen Zeichen im Dateinamen vor dem Abschicken des Formulars), da der Dateiname bereits verstümmelt wird, wenn er in die Variable $_FILES geschrieben wird und daher dort nicht mehr überprüft werden kann.

Zulässiges Upload-Limit einstellen #

Üblicherweise ist das Limit für den Upload von Dateien auf dem Webserver in der Konfigurationsdatei php.ini auf 2 MB voreingestellt. Unter Umständen möchte man aber auch größere Dateien hochladen können. Dazu lassen sich die für diesen Vorgang wesentlichen Umgebungsvariablen der PHP-Konfigurations überschreiben.

Um zu erfahren, welche Werte hier voreingestellt sind, kann man folgendes Skript ausführen:

Quelltext auswählen
1
2
3
4
5
6
7
<?php
  echo "post_max_size = ".ini_get('post_max_size')."<br>";
  echo "upload_max_filesize = ".ini_get('upload_max_filesize')."<br>";
  echo "memory_limit = ".ini_get('memory_limit')."<br>";
  echo "max_execution_time = ".ini_get('max_execution_time')."<br>";
  echo "max_input_time= ".ini_get('max_input_time');
?>

Legt man nun eine eigene php.ini-Datei im Wurzelverzeichnis des eigenen Webspace an, so können die in dieser Datei enthaltenen Variablen die voreingestellten Werte überschreiben, wie im Beispiel unten. Welche Werte für die eigenen Zwecke sinnvoll sind, muss man selbst herausfinden, wobei man sie nicht zu großzügig wählen sollte, um den Server nicht zu belasten.

Weitere Infos: PHP Manual

Quelltext auswählen
1
2
3
4
5
post_max_size = 8M
upload_max_filesize = 2M
memory_limit = 16M
max_execution_time = 30
max_input_time= 60

Downloads #

Im obigen Beispiel kann man über einen normalen Verweis auf die Datei im Upload-Verzeichnis diese auch herunterladen. Bestimmte Dateitypen (wie Grafiken oder Office-Dateien) würden allerdings im Browser selbst angezeigt werden. Dies lässt sich mit einem einfachen PHP-Skript (hier: download.php) umgehen, auf das anstelle der gewünschten Datei selbst verwiesen wird.

Quelltext auswählen
1
2
3
4
5
6
7
<?php
  if ($_GET['file'] == basename($_GET['file'])) {
    header("content-type: application/octet-stream");
    header("content-disposition: attachment; filename=\"".str_replace("\\'", "'", $file)."\"");
    readfile("uploads/".stripslashes($_GET['file']));
  }
?>

Dieses Skript stellt in Zeile 2 zunächst sicher, dass die Query für file keine Pfadangabe, sondern nur einen Dateinamen enthält. Dies könnte der Fall sein, wenn jemand den Pfad einer nicht für den Download bestimmten Datei in einem anderen Verzeichnis manuell als Query anhängt und dann das Skript aufruft, um die Seite auf diese Weise zu hacken. So könnte man jede beliebige Datei der Website auch aus anderen Verzeichnissen heraus downloaden. Eine andere, etwas aufwendigere Methode, um unerlaubte Downloads zu verhindern, ist es, in einer Datenbank den Pfad jeder für den Download zugelassenen Datei zu speichern und im Downloadlink statt des Dateinamens lediglich die ID des Datenbankeintrags zu übergeben, die dann erst im Downloadskript selbst per Datenbankabfrage zur Pfadangabe verwandelt wird.

Die Funktionen str_replace() und stripslashes() sind hier notwendig, damit das Skript Dateien verarbeiten kann, deren Namen bestimmte Sonderzeichen enthalten.

Über eine Query wird der Dateiname von der Datei upload.php an das Skript geschickt. Dazu wird die Datei upload.php in Zeile 28 folgendermaßen geändert:

Quelltext auswählen
1
echo "<tr><td><samp><a href='download.php?file=".rawurlencode($file)."'>".$file."</a></samp></td>";