File Upload

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Eine der häufigsten Aufgaben im Webdesign ist das Senden von Formulareingaben. Oft will man jedoch nicht nur Daten, sondern ganze Dateien wie Bilder, PDFs oder Text-Dokumente hochladen.

Dieses Tutorial zeigt, wie Sie ein Eingabeformular für den Datei Upload erstellen und welche browserseitigen Sicherheitsvorkehrungen Sie beachten müssen.

input type="file"

Mithilfe des Formularelements input type="file" kann der Anwender eine Datei von seinem lokalen Rechner zusammen mit dem Formular übertragen (Upload). Durch das Attribut type="file" erstellt der Browser automatisch ein Eingabefeld für den Pfad zur Datei und einen Button mit dessen Hilfe eine lokale Datei ausgewählt werden kann. Die Größe des Eingabefeldes kann wie üblich mit dem size-Attribut festgelegt werden.

Dieses Element funktioniert nur mit der POST-Methode und nicht mit GET. Außerdem muss im Formular das enctype="multipart/form-data"-Attribut notiert werden, da sonst nur der Name und nicht die Datei selbst übertragen wird.

Beachten Sie: Beim Absenden des Formulars wird zwar die gewählte Datei auf den Server übertragen, jedoch nur in einem temporären Verzeichnis gespeichert. Am Ende des Requests wird sie wieder verworfen. Es bedarf serverseitig eines Scriptes (PHP, Node.js, JSP, …), um die Datei nach dem Upload dauerhaft in ein anderes Verzeichnis auf dem Server abzulegen. Die serverseitige Perspektive wird im Folge-Kapitel PHP/Tutorials/File Upload behandelt.

accept

Mithilfe des accept-Attributs kann angegeben werden, welche Dateitypen für den Upload vorgesehen sind. Sie können in diesem Attribut eine durch Komma getrennte Liste von Dateitypangaben hinterlegen, die der Browser dafür nutzen kann, die Dateien, aus denen der Anwender im Dateidialog auswählen kann, vorzufiltern. Der Anwender kann diese Filterung jedoch übergehen. Als Dateityp können Sie auf Computern, die Extensions benutzen, eine File Extension (.txt, .png, .exe) verwenden, eine MIME-Typangabe (text/plain) oder eine MIME-Typ Sammelangabe ("audio/*). Die im Beispiel verwendete Angabe text/* ist hingegen zwiespältig zu sehen: Die HTML Spezifikation nennt ausdrücklich nur audio, video und image als gültige Typen für Sammelangaben. Es kann also sein, dass der Browser diese accept-Angabe ignoriert und dem Anwender einfach alle Dateien anbietet.

Upload von Text-Dateien So sieht's aus
<form method="post" enctype="multipart/form-data">
  <label>Wählen Sie eine Textdatei (*.txt, *.html usw.) von Ihrem Rechner aus.
    <input name="datei" type="file" size="50" accept="text/*"> 
  </label>  
  <button>… und ab geht die Post!</button>
</form>

Hier ist die Breite des Eingabefeldes auf 50 Zeichen festgelegt und es sind alle Textdateien erlaubt. Zusätzlich sei noch erwähnt, dass es sinnvoll ist einen Submit-Button hinzuzufügen, da diverse Browser neueren Datums ein unmittelbares Ausführen über das Textfeld mit dem Dateipfad der gewählten Datei der Aktion nicht mehr zulassen.

Beachten Sie: Da die accept-Angabe nur eine Komfortfunktion für den Anwender ist und im Dateiauswahldialog überschrieben werden kann, muss serverseitig zwingend überprüft werden, ob die hochgeladene Datei den erwarteten MIME-Typ hat.

mehrere Dateien auf einmal hochladen

Mit dem booleschen Attribut multiple kann eine Mehrfachauswahl zugelassen werden, sodass anschließend mehrere Dateien vom Browser an den Server übermittelt werden. Damit die verwendete serverseitige Script-Sprache mit mehreren Dateien zu diesem input-Feld umgehen kann, ist es unter Umständen notwendig einen geeigneten Namen im name-Attribut zu notieren (PHP benötigt z. B. ein eckiges Klammernpaar wie name="datei[]", damit nicht nur die letzte übertragene Datei erkannt wird).

mehrere Dateien hochladen
<form action="manage_uploads.php" method="post" enctype="multipart/form-data">
  <label>Wählen Sie die hochzuladenden Dateien von Ihrem Rechner aus:
    <input name="datei[]" type="file" multiple> 
  </label>  
  <button>hochladen</button>
</form>

Der empfangende Webserver kann aus z. B. Sicherheitsgründen ein Limit für die Anzahl und die Datenmenge der zu empfangenden Dateien haben. Als HTML-Autor haben Sie darauf keinen Einfluss und selbst als Programmierer der serverseitigen Programmlogik haben Sie unter Umständen keine Möglichkeit diese Einstellungen des Servers zu ändern. In solchen Fällen benötigt man eine Lösung, bei der vom Browser die Datenmenge in passend kleinen Teilen übertragen wird, um sie dann auf der Serverseite wieder zusammen zu setzen.

File API

Mit JavaScript und der HTML5 File API können Sie nun ihr HTML-Formular durch weitere Funktionen erweitern, die dem Benutzer der Seite mehr Komfort bieten.[1][2]

Interessant ist hierbei vor allem, dass die APIs die Möglichkeit bieten den Uploadfortschritt zu verfolgen, Thumbnails (Miniaturansichten) der Dateien anzuzeigen, sowie der Einsatz von Drag and Drop. Letzteres ist sehr bequem um Dateien vom Desktop oder aus Ordnern direkt in die Webanwendung zu ziehen, in die die Datei geladen werden soll.


Multi Upload von Dateien

Das Eingabefeld besteht eigentlich aus mehreren im Shadow DOM versteckten Elementen:

"screenshot eines File-Upload-Elements in Chrome 109"

Bei der Auswahl einer Datei wird deren Name im versteckten span-Element angezeigt. Bei mehreren ausgewählten Dateien erscheint aber nur die Anzahl. Um dem Benutzer die Namen, MIME-Typen und auch die Dateigröße anzuzeigen, erweitern wir unser Code-Snippet aus dem letzten Abschnitt.


Anzeige der ausgewählten Dateien So sieht's aus
document.getElementById('dateien').addEventListener('change', dateiauswahl, false);

function dateiauswahl(evt) {
  // FileList-Objekt des input-Elements auslesen, 
  // wenn das change-Event ausgelöst wurde (event.target)
  var files = evt.target.files; 

  // Deklarierung eines Array Objekts mit Namen "fragmente". Hier werden die Bausteine
  // für die erzeugte Listenausgabe gesammelt.
  var fragmente = [];
  // Zählschleife; bei jedem Durchgang den Namen, Typ und 
  // die Dateigröße der ausgewählten Dateien zum Array hinzufügen
  for (var i = 0, f; f = files[i]; i++) {
    fragmente.push('<strong>', f.name, '</strong> (', f.type || 'n/a', ') - ', f.size, ' bytes','<br>');
  }
  // Alle Fragmente im fragmente Array aneinanderhängen und 
  // alles als HTML-Inhalt in das output-Element mit id='dateiListe' einsetzen.
  document.getElementById('dateiListe').innerHTML = fragmente.join('');
}

Sobald im Formular etwas geändert wird, feuert das change-Event und ruft die Funktion dateiauswahl() auf.

Sie liest das FileList-Objekt des input-Elements aus. Anschließend wird ein Array fragmente angelegt. In diesem Array werden mittels einer Zählschleife für jede ausgewählte Datei deren Dateiname, -typ, sowie Größe gespeichert (über die Arraymethode push()).

Auf eine Formatierung wird bis auf die beiden als Strings eingefügten strong-Tag und das folgende br verzichtet.

document.getElementById('list').innerHTML = fragmente.join(''); setzt all diese Fragmente mittels der Arraymethode join() zu einer langen Zeichenkette zusammen.

Die Registrierung des change-Events, die zum Aufruf der dateiauswahl()-Funktion erforderlich ist, erfolgt in einem DOMContentLoaded-Handler, damit das Script im <head> der Seite stehen kann.

Auswahl mit Bildvorschau

Noch übersichtlicher ist das Anzeigen kleiner Vorschaubilder - sogenannter Thumbnails.

Dateiupload mit Thumbnails So sieht's aus
  for (var i = 0, f; f = files[i]; i++) {
    ...
									 
      // nur Bild-Dateien
      if (!f.type.match('image.*')) {
        continue;
      }

      var reader = new FileReader();

      reader.onload = (function(theFile) {
        return function(e) {
          // erzeuge Thumbnails.
          var vorschau = document.createElement('img');
		  vorschau.className = 'thumb';
		  vorschau.src   = e.target.result;
		  vorschau.title = theFile.name;
          document.getElementById('thumblist').insertBefore(vorschau, null);
        };
      })(f);

      // Bilder als Data URL auslesen.
      reader.readAsDataURL(f);									 
  }
  ...
	
}

In diesem Beispiel werden von den hochzuladenden Bildern Vorschaubilder erstellt (Thumbnailing), die dann im Ausgabefeld output-Element angezeigt werden.

Beachten Sie: Textdateien werden nicht dargestellt.

Auswahl mit Drag und Drop

ToDo (weitere ToDos)

ab hier überarbeiten! --Matthias Scharwies (Diskussion) 09:59, 21. Jan. 2023 (CET)
alternative Auswahl mit Drag &Drop So sieht's aus
  function dateiauswahl(evt) {
    evt.stopPropagation();
    evt.preventDefault();

    var gewaehlteDateien = evt.dataTransfer.files; // FileList Objekt


    var output = [];
    for (var i = 0, f; f = gewaehlteDateien[i]; i++) {
      output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModified.toLocaleDateString(), '</li>');
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; 
  }

  // Initialisiere Drag&Drop EventListener
  var dropZone = document.getElementById('dropzone');
  dropZone.addEventListener('dragover', handleDragOver, false);
  dropZone.addEventListener('drop', dateiauswahl, false);

Hier können Sie Bilder und Dateien aus Ihrem Betriebssystem bequem mit der Maus (Drag & Drop) ziehen und im div mit der id dropzone ablegen.

Anschließend wird die ausgewählte Datei wie im Beispiel 1 mit einigen Eigenschaften aufgelistet.

Direkter Upload einer einzelnen Datei an einen Server

ToDo (weitere ToDos)

Beispiel mit fetch --Matthias Scharwies (Diskussion) 09:59, 21. Jan. 2023 (CET)
Beispiel
  function dateiupload(evt) {
    var dateien = evt.target.files; // FileList objekt

    // erste Datei auswählen (wichtig, weil IMMER ein FileList Objekt generiert wird)
    var uploadDatei = dateien[0];

    // Ein Objekt um Dateien einzulesen
    var reader = new FileReader();

    var senddata = new Object();
    // Auslesen der Datei-Metadaten
    senddata.name = uploadDatei.name;
    senddata.date = uploadDatei.lastModified;
    senddata.size = uploadDatei.size;
    senddata.type = uploadDatei.type;

    // Wenn der Dateiinhalt ausgelesen wurde...
    reader.onload = function(theFileData) {
      senddata.fileData = theFileData.target.result; // Ergebnis vom FileReader auslesen

      /*
      Code für AJAX-Request hier einfügen
      */
    }

    // Die Datei einlesen und in eine Data-URL konvertieren
    reader.readAsDataURL(uploadDatei);
  }

Dank der Möglichkeit, eine Datei als Data-URL einzulesen, ist es möglich, selbst mit einem gewöhnlichen Webformular eine Datei an einen Server zu senden.

Beachten Sie: Um die Datei auf dem Server abzuspeichern, muss dieser "nur" den Base64-kodierten Teil der Daten-URL wieder in seinen ursprünglichen Bytecode zurückkonvertieren. Der Datentyp, der der Daten-URL beigefügt wird, dient dabei als doppelte Absicherung, um Betrugsfälle abzuwehren.


Auswahl mit Textvorschau

Beispiel
  function dateiauswahl(evt) {
    var dateien = evt.target.files; // FileList object

    // Auslesen der gespeicherten Dateien durch Schleife
    for (var i = 0, f; f = dateien[i]; i++) {

      // nur TXT-Dateien
      if (!f.type.match('text/plain')) {
        continue;
      }

      var reader = new FileReader();

      reader.onload = (function(theFile) {
        return function(e) {
          // erzeuge "Thumbnails"
          var vorschau = document.createElement('p');
		  vorschau.className = 'thumb';
		  vorschau.src   = e.target.result;
		  vorschau.title = theFile.name;
          document.getElementById('list').insertBefore(vorschau, null);
        };
      })(f);

      // Klartext mit Zeichenkodierung UTF-8 auslesen.
      reader.readAsText(f, utf8);
    }
  }
  // Auf neue Auswahl reagieren und gegeenenfalls Funktion dateiauswahl neu ausführen.
  document.getElementById('files').addEventListener('change', dateiauswahl, false);

In diesem Beispiel wird von den hochzuladenden Texten der komplette Inhalt übernommen, welcher dann im Ausgabefeld output-Element angezeigt wird.

Beachten Sie: Diese Methode ist nur für TXT-Dateien sinnvoll.
Beachten Sie: Sie müssen bereits im Vorfeld die exakte Zeichenkodierung der Textdateien kennen. Wird die Angabe weggelassen, wird die Datei mit der Zeichenkodierung UTF-8 ausgelesen.

(billiger) Hex-Viewer im Browser

Beispiel
  function dateiauswahl(evt) {
    var dateien = evt.target.files; // FileList object

    // Auslesen der gespeicherten Dateien durch Schleife
    for (var i = 0, f; f = dateien[i]; i++) {

      var reader = new FileReader();

      reader.onload = (function(theFile) {
        return function(e) {
          // Bytedaten übernehmen
          var byteBlock = e.target.result;  // ArrayBuffer Objekt
          // ein Objekt um den Byteblock auszulesen
          var dataViewer = new DataView(byteBlock);
          var hexView = "";
          // Hex-Werte übernehmen
          for (var j = 0; j < f.size; j++) {
            hexView += parseInt(dataViewer.getUint8(j), 16) + " ";
          }
          // Das Leerzeichen am Ende entfernen
          hexView = hexView.substr(0,hexView.length-1);
          // erzeuge "Thumbnails"

          var vorschau = document.createElement('p');
		  vorschau.className = 'thumb';
		  vorschau.src   = hexView
		  vorschau.title = theFile.name;
          document.getElementById('list').insertBefore(vorschau, null);
        };
      })(f);

      // Datei als ByteArray auslesen auslesen.
      reader.readAsArrayBuffer(f);
    }
  }
  // Auf neue Auswahl reagieren und gegebenenfalls Funktion dateiauswahl neu ausführen.
  document.getElementById('files').addEventListener('change', dateiauswahl, false);

Hiermit können die Hexwerte jeder beliebigen Datei ausgelesen werden.

Beachten Sie: Das DataView-Objekt ist so mächtig, dass man hiermit einen voll funktionsfähigen Hex-Editor im Browser erstellen kann. Daher sollte der Gebrauch des ArrayBuffer- und des DataView-Objekts mit Vorsicht genossen werden.

Quellen

  1. W3C: File API
  2. MDN: File API