Icon [ zurück ]

formularbasierte Dateiübertragung per HTML

( zu meinem Artikel in "Der Entwickler 06/2001" )


In der Zeitschrift "Der Entwickler" Heft 06/2001 habe ich beschrieben, wie man Dokumente ohne FTP auf eine Webseite hochlädt. Ganz simpel von einem normalen HTML-Formular aus! An dieser Stelle finden Sie wesentliche Gedanken dieses Artikels sowie einige weiterführende Betrachtungen.

Mit dem Verfahren "formularbasierte Dateiübertragung auf Webserver per HTML" bieten sich dem Webmaster einige neue Möglichkeiten. Im Intranet können so auch "web-ungeübte" Mitarbeiter selbst verfasste Dokumente unkompliziert per Browser in einem speziell für sie reservierten Bereich des Intranets veröffentlichen. Im Internet dagegen schafft diese Technik überhaupt erst eine (zudem auch noch recht sichere) Möglichkeit, dass beliebige Benutzer ohne Kenntnis der offiziellen Zugangsdaten zur Webseite ihre Dokumente kontrolliert (beispielsweise nur Bilder) heraufladen.

Grundlagen

Die Grundlagen für ein solches Dateiübertragungsverfahren werden in dem Memo RFC 1867 geregelt. Es handelt sich um "formularbasierte Dateiübertragung per HTML". Diese Norm ist mit Garantie in jedem üblichen Browser implementiert.

Normalerweise enthält ein HTML-Formular ein oder mehrere Eingabefelder, deren Werte beim Abschicken des Formulars vom Browser an den Webserver gesendet und dort durch eine interaktive Servererweiterung entgegengenommen, ausgewertet und verarbeitet werden. Für gewöhnlich handelt es sich dabei um eine eher bescheidenen Datenmenge aus wenigen Eingabefeldern. Egal ob die Methode POST oder GET für das Formularhandling verwendet wird, die Daten werden vom Browser standardmäßig "application/x-www-form-urlencoded" verschlüsselt. Das ist in RFC 1866 so geregelt.

Eine besondere Funktion ist mit dem Formularfeld vom Typ Datei
  <input type="file">
verbunden. Wird ein solches besonderes Feld verwendet und die Formularaktion mit dem Zusatz
  enctype="multipart/form-data"
versehen, dann wird nicht nur der in das Eingabefeld eingetragene Dateiname zum Server gesendet, sondern auch der gesamte Dateiinhalt beliebigen Datentyps.

Formular

Das Datenformat bei der Verschlüsselungsmethode "multipart/form-data" unterscheidet sich erheblich von dem sonstiger Formulardaten gemäß "www-form-urlencoded". Jedes Eingabeelement mit seinem Namen und Wert bildet einen eigenen Abschnitt im Datenstrom, der vom Vorgänger und Nachfolger durch eine Begrenzungszeile boundary isoliert ist. Das folgende Muster zeigt die Struktur für einen einfachen Fall.

  Content-type: multipart/form-data, boundary=---AaB03x
  -----AaB03x
  content-disposition: form-data; name="user"

  Einkauf
  -----AaB03x
  content-disposition: form-data; name="pass"

  geheim
  -----AaB03x
  content-disposition: form-data; name="upfile"; filename="aktuell.htm"
  Content-Type: text/html

  ... Inhalt von aktuell.htm ...
  -----AaB03x--

Einige Browser gestatten Mehrfachauswahl im Dateinamensfeld. Dann ändert sich das Format der gesendeten Daten noch einmal. Das Dateifeld seinerseits ist bei Auswahl mehrerer zu übertragender Dateien "multipart/mixed" kodiert und hat eine eigene, verschachtelte Boundary-Zeile, wie das folgende Beispiel verdeutlichen soll.

  Content-type: multipart/form-data, boundary=---AaB03x
  -----AaB03x
  content-disposition: form-data; name="user"

  Einkauf
  -----AaB03x
  content-disposition: form-data; name="pass"

  geheim
  -----AaB03x
  content-disposition: form-data; name="files"
  Content-type: multipart/mixed, boundary=---BbC04y

  -----BbC04y
  Content-disposition: attachment; filename="file1.txt"

  Content-Type: text/plain

  ... Inhalt von file1.txt ...
  -----BbC04y
  Content-disposition: attachment; filename="file2.gif"
  Content-type: image/gif
  Content-Transfer-Encoding: binary

  ...Inhalt von file2.gif...
  -----BbC04y--
  -----AaB03x--

Das sind im Wesentlichen die Kerngedanken aus RFC 1867. Nun muss der Server lernen, darauf entsprechend zu reagieren.

eine Servererweiterung muss her

Zur Aufnahme des Datenstromes benötigen Sie eine passende Servererweiterung. Diese hat folgenden Aufgabenumfang abzuarbeiten:

Natürlich kann der Webmaster durch passende Gestaltung des Upload-Formulars den Vorgang möglichst einfach gestalten. Wenn die Benutzerautorisierung im Formular mit Sicherheit vollständig vor den Dateidaten angeordnet ist, ergibt sich ein einfacherer Ablauf des Scripts. Kann das wie in den meisten praktischen Fällen nicht garantiert werden (Formulare werden halt immer wieder mal neu "layoutet" und angepasst), ist eine aufwendigere aber zugleich universellere und sicherere Implementierung der Serveraktivität zu empfehlen. Es kann dann nämlich passieren, dass die Dateidaten vor den Userdaten im Datenstrom liegen, also zuerst die Datei eintrifft und noch nicht entschieden werden kann, ob und wohin diese zu speichern ist.

Kernimplementierung mit Delphi

Im Quelltextarchiv finden Sie eine Implementierung der Kernfunktion als Bibliothek. Eine Funktion namens GetPostedContent() aus meiner Bibliothek PostContent.pas übernimmt die gesamte Detailarbeit. Von dieser Bibliothek finden Sie zwei Varianten vor. Eine Profi- und eine Standard-Version.

Die Profi-Version ist für die Verwendung in Webapplikationen gedacht, die auf der Basis der HttpApp-Objekte aus den Client-/Server- bzw. Enterprise-Versionen von Delphi erstellt werden. In den Webapplikationen sowohl vom Typ CGI als auch vom Typ ISAPI ist zwar ein recht leistungsfähiges Objekt TWebRequest vorhanden, das behandelt aber nur www-form-urlencoded Daten korrekt. Mit ganzen Dateiströmen als multipart/form-data kann TWebRequest nicht umgehen. GetPostedContent() erweitert quasi dessen Funktionalität und arbeitet mit dem Objekt zusammen. Deshalb ist der erste Parameter des Funktionsaufrufes ein Verweis auf das überforderte TWebRequest-Objekt.

Die Standard-Variante von PostContent.pas ist für handgeschriebene "objektlose" CGI- oder ISAPI-Applikationen gedacht. Die Funktion GetPostedContent() hat hier exakt den gleichen Leistungsumfang, allerdings andere Aufrufparameter als in der Profi-Variante. Beachten Sie bitte, dass bei Wechsel zwischen ISAPI- und CGI-Projekten diese Bibliothek jeweis neu übersetzt werden muß.

Alle weiteren Detail entnehmen Sie bei Interesse den kommentierten Quelltexten. Stolpern Sie nicht über den umfangreichen Mittelteil der Funktionsimplementierung. Er dient dem (seltenen) Fall, dass mehrere Files aus einem Formularfeld heraus als multipart/mixed übertragen werden. Weder der MS-IE 5.5 noch der Netscape 4.7 unter Windows erlauben aber Mehrfachselektion bei der Dateinamenseingabe. (Es soll da aber noch andere Browser und Betriebssysteme geben!)

komplettes Demo in Varianten

Egal ob Sie sich für eine CGI- oder eine ISAPI-Lösung entscheiden, die eigentliche Servererweiterung für die Datenbehandlung ist durch Nutzung der vorgestellten Bibliothek recht einfach zu implementieren.

Ich ermittle im zugrunde liegenden Beispiel zunächst das temporäre Systemverzeichnis und rufte mit diesem als Parameter die beschriebene Funktion GetPostedContent() auf. Im Ergebnis des Funktionsaufrufes werden alle vom Browser gesandten Files im TEMP-Verzeichnis hinterlegt und sämtliche sonstige Formularinformationen einschließlich Benutzername und Passwort als Wertepaare über den Stringlisten-Parameter zurückgegeben.

An dieser Stelle sei auf eine mögliche Fehlerquelle hingewiesen: Es ist im Web vorstellbar, dass zwei Benutzer unabhängig voneinander gleichzeitig eine Datei heraufladen, die zufällig den selben Namen führt. Das würde zu Konflikten im temporären Verzeichnis führen. Deshalb benutzt das Funktionsmuster noch die jeweilige Thread-ID zur eindeutige Benennung der Dateien im Zwischenpuffer.

Nun prüft die Hauptaktion anhand der entschlüsselten Formularfelder user und pass die Upload-Rechte des Benutzers. Inwieweit Sie dabei eine echte Datenbankabfrage implementieren, richtet sich nach den Verhältnissen in Ihrem Web. Mein Muster liest die Berechtigungen einfach aus einer Ini-Datei UpLoad.ini als Datenbankersatz, die im selben Verzeichnis wie die Webapplikation hinterlegt ist. Weil das gewöhnlich das /cgi-bin-Verzeichnis ist, bleibt diese Ini-Datei auch dem direkten Lesezugriff neugieriger Surfer vorenthalten.

Je nach Berechtigung des Klienten werden die hochgeladenen Dateien aus dem temporären Speicherverzeichnis in den persönlichen Web-Ordner des Benutzers verschoben oder bei fehlenden Rechten halt einfach nur wieder gelöscht. Das konkrete Directory für die endgültige Ablage ist ebenfalls in der Ini-Datenbank enthalten. Eine passende Textmeldung für die Antwortseite wird vorbereitet.

Tip: Wenn Sie mit meinem Original-Demoprogramm jedermann den Dateiupload in ein "Müll"-Verzeichnis Ihres Webs erlauben wollen, führen Sie die Formularfelder user und pass einfach als hidden-Felder aus.

Ausgabe

Am Ende aller Datenverarbeitung erfolgt das Erzeugen der endgültigen Antwortseite der Serverapplikation. Der Hauptteil der Antwortseite kommt dabei aus einer HTML-Schablone namens upload.htm, die ebenfalls im /cgi-bin-Verzeichnis direkt neben dem Programm bereitliegt. Diese enthält die Spezialtags <#msg>, <#date> und <#time>, die bei der Generierung der Ausgabeseite durch aktuelle Daten ersetzt werden.

 <html>
  <head><title>Ergebnis</title></head>
  <body background="/texture.gif">
   <h1>UpLoad-Ergebnis</h1>
   <hr>
   <p><#msg></p>
   Bei erfolgter Speicherung können Sie ab sofort
   auf diese Datei über Ihre Webseite zugreifen.
   <hr>
   <p align="center"><small><#date>, <#time></small></p>
   </body>
 </html>

Hier sei noch auf eine zweite Problemstelle hingewiesen: Während bei CGI-Anwendungen zum Lesen einer Vorlagedatei aus dem selben Verzeichnis einfach nur der Dateiname ohne eine Pfadangabe ausreichend wäre, führt das bei ISAPI-Anwendungen zum Fehler. ISAPI-Extensions laufen nämlich nicht in ihrem Speicher-Verzeichnis, sondern direkt im Umgebungsraum des Webservers. Über einen Aufruf von GetModuleFilename() kann man aber doch ergründen, wo die ISAPI-DLL eigentlich gespeichert ist.

Quelltextarchiv

Das vorgestellte Beispiel in den Realisierungen als CGI- oder ISAPI-Lösung ist nur eine Demonstration, wie mit weniger als 100 selbst geschriebenen Codezeile eine effektvolle und sichere Erweiterung Ihres Webs erfolgen kann. Für Veränderungen im Detail finden Sie bestimmt einen Ansatz. Die Funktion GetPostedContent() aus der Unit PostContent.pas erspart Ihnen auf alle Fälle, sich um das Kleingedruckte aus RFC 1867 kümmern zu müssen.

Disk das Quelltext-Archiv für Delphi 3..6 (28 KByte)

Disk RFC 1867, RFC 1866

© 2001     Jürgen Hummel

[ Programmierung ]