pl: multipart/form-data

Damit mir dieser Schrott endlich mal ans Herz wachsen soll, hab ich das beim Entwickeln meines Universalparsers mit aufgenommen. Ein Parser muss hier durch, untenstehend ein einzelner Part:

--boundary
Content-Type: image/gif name="bild" filename="foo.gif"

BINARY
--boundary

und das wird insofern umständlich als dass der Parser über die BINARY hinaus lesen muss, solage nämlich bis er wieder eine --boundary im Puffer hat. Das bis dahin Gelesene ist also um die Länge der Boundary zu kürzen und der Pointer im Handle um diesen Betrag zurückzusetzen. Schöner wärs doch, wenn es zu jedem Part einen Eintrag

--boundary
Content-Length: 522

geben würde, dann stehts von vornherein fest, wieviele Bytes zu lesen sind. Frage an die Experten: Werden zukünftige Browser oder XHR eine Content-Length in solchen Fällen liefern? In der Tat wäre ein Parser einfacher zu programmieren. pl

  1. Hallo pl,

    Frage an die Experten: Werden zukünftige Browser oder XHR eine Content-Length in solchen Fällen liefern?

    Nein.

    LG,
    CK

    P.S.: NIH?

    1. P.S.: NIH?

      application/x-www-form-urlencoded
      multipart/form-data
      application/json
      application/soap+xml
      weitere...multipart/c-eav, multipart/eav...
      application/octet-stream
      

      Bisherige Parser unterstützen nur die ersten Beiden. Mein Universalparser untersützt alle in obenstehender Liste und liefert stets dieselben Datenstrukturen.

      Dafür mnuss ein Client nur den richtigen Content-Type-Header mitsenden. Darüber nachzudenken hat sich gelohnt (aber sowas von!!!) das spart in Zukunft ne Menge Entwicklungsarbeit.

      D.h., ich habe fertig und etwaige Erweiterung (Enctype) hängen nur noch dvon ab, ob serverseitig die entsprechenden Libs zur Verfügung stehen. pl

      (Und keine /tmp-Dateien bei multipart/form-data)

  2. --boundary
    Content-Type: image/gif name="bild" filename="foo.gif"
    
    BINARY
    --boundary
    

    und das wird insofern umständlich als dass der Parser über die BINARY hinaus lesen muss, solage nämlich bis er wieder eine --boundary im Puffer hat.

    Die meisten Parser benutzen dafür sogenannte Tokenizer, die den Text in logische Einheiten gliedern. In diesem Fall, könnte ein Tokenizer den Text beispielsweise so zerlegen:

    --boundary
    
    Content-Type: image/gif name="bild" filename="foo.gif"
    
    BINARY
    
    --boundary
    

    Diese Tokens benutzt der Parser dann als Eingabe und ergänzt die nötige Struktur. In dem konkreten Beispiel würde ein Tokenizer vermutlich auch noch gleich die Header-Daten der Formular-Fragmente zerlegen.

    Das bis dahin Gelesene ist also um die Länge der Boundary zu kürzen und der Pointer im Handle um diesen Betrag zurückzusetzen. Schöner wärs doch, wenn es zu jedem Part einen Eintrag

    --boundary
    Content-Length: 522
    
    

    geben würde, dann stehts von vornherein fest, wieviele Bytes zu lesen sind.

    Es gibt Kodierungen, wie bswp. PHPs serialize() die machen das wirklich so. Für die Formularkodierung wäre das ungünstig: Wenn große Datenmengen übertragen werden sollen, würde der Upload-Vorgang verzögert werden bis die Datenmenge ermittelt ist. Gerade bei kontinuirlichen Datenströmen, deren Größe man nicht im Voraus kennt, ist das ein Problem, weil man eigentlich schon während des Lesens des Streams parallel Daten hochladen könnte.

    Frage an die Experten: Werden zukünftige Browser oder XHR eine Content-Length in solchen Fällen liefern? In der Tat wäre ein Parser einfacher zu programmieren.

    Parser sind heutzutage schon so weit erforscht, dass man sie vollautomatisch aus Spezifikationen der Sprachsyntax erzeugen lassen kann. Solche Spezifikation liegen meistens als kontextfreie Grammatiken vor, damit lassen sich die Parser-Generatoren füttern und heraus kommen einsatzbereite Parser.

    1. hi,

      Es gibt Kodierungen, wie bswp. PHPs serialize() die machen das wirklich so. Für die Formularkodierung wäre das ungünstig: Wenn große Datenmengen übertragen werden sollen, würde der Upload-Vorgang verzögert werden bis die Datenmenge ermittelt ist. Gerade bei kontinuirlichen Datenströmen, deren Größe man nicht im Voraus kennt, ist das ein Problem, weil man eigentlich schon während des Lesens des Streams parallel Daten hochladen könnte.

      Das ist interessant und Grund es so zu machen. Genau dieselbe oder zumindest ähnliche Frage stand im Raum bei der Entwicklung von HTTP/1.0 zu HTTP/1.1, Issue: persistent Connection (Keep-Alive).

      Gelöst wurde es über Response Transfer-Encoding: chunked, da sind die Längenangaben den chunks beigefügt. Und auch der Algorithmus zum Deserializieren ist im RFC2616 dokumentiert (nachdem ich von selbst drauf gekommen bin, hab ich das in irgendeiner RFC gelesen *G ).

      Nunja, chunked oder Content-Length, multipart/form-data in meinen UniParser mit aufzunehmen, Ehrensache und ohne das Monster CGI.pm ;)

      Die nächsten Tage => Neuer Content-Type: application/rpc-eav und evntl noch /cms-eav. Proprietär aber /rpc-eav liefert nach dem Parsen exakt dasselbe wie /xml+rpc nur dass die Daten anders verpackt sind. Ich hab ne ganze Menge an Webservices, RPC-Zeugs und Content-Management per HTTTP zu machen, das ist z.T. noch in diskreten .cgi-Dateien verstreut. Mit meinem neuen UniParser wird das alles Eins und läuft über dasselbe Framework was auch ganz normal HTML oder sonstigen Content ausliefert.

      Über die Tragweite meines neuen Parsers bin ich mir gar noch nicht so richtig klargeworden, eigentlich wäre das ein riesen Grund zum Feiern, allein schon die Idee..... pl

      --
      Ohje: Ich bin Thüringer und hab Sachsenblut.... ... an den Händen :D
    2. --boundary
      Content-Type: image/gif name="bild" filename="foo.gif"
      
      BINARY
      --boundary
      

      und das wird insofern umständlich als dass der Parser über die BINARY hinaus lesen muss, solage nämlich bis er wieder eine --boundary im Puffer hat.

      Die meisten Parser benutzen dafür sogenannte Tokenizer, die den Text in logische Einheiten gliedern.

      Ein echter Serializer macht es im konkreten Fall so (deserialize):

      • Länge des mitgelieferten Boundary-String ermitteln ($blen = length $boundary_string)
      • lese $blen aus STDIN, stehe danach vor einem Zeilenumbruch,
      • lese nun in Schritten von genau 1 byte weiter um den nächsten Zeilenumbruch zu finden,
      • wenn ich den Zeilenumbruch habe, parse ich die bisherigen Daten name=; filename=; Content-Type...
      • erstelle nun ein tmp/-Handle, lese aus STDIN in Schritten von 1 Byte weiter und kopiere die gelesenen Bytes in das /tmp-Handle
      • beende das Lesen aus STDIN, wenn ich im Puffer den Boundary-String entdecke,
      • schneide den Boundary-String vom /tmp-Handle ab
      • sichere die gewonnenen Daten in meinem Parser-Objekt
      • wiederhole den hier beschriebenen Algorithnmus solange, bis keine Daten mehr kommen.

      Da ich mich in STDIN nicht per seek() bewegen kann, kopiere ich STDIN nach IO::String bevor ich überhaupt anfange. Das /tmp-Handle kann ebenfalls ein IO::String-Objekt sein oder eine temporäre Datei.

      Übrigens: Eine Boundary zu setzen die mit haufenweise Bindestrichen beginnt, ist irgendwie auch bescheuert weil 2 Bindestriche sowohl vorn als auch hinten angefügt werden (viel Spaß beim Debuggen). pl

      1. Ein echter Serializer macht es im konkreten Fall so (deserialize):

        • Länge des mitgelieferten Boundary-String ermitteln ($blen = length $boundary_string)
        • lese $blen aus STDIN, stehe danach vor einem Zeilenumbruch,
        • lese nun in Schritten von genau 1 byte weiter um den nächsten Zeilenumbruch zu finden,
        • wenn ich den Zeilenumbruch habe, parse ich die bisherigen Daten name=; filename=; Content-Type...
        • erstelle nun ein tmp/-Handle, lese aus STDIN in Schritten von 1 Byte weiter und kopiere die gelesenen Bytes in das /tmp-Handle
        • beende das Lesen aus STDIN, wenn ich im Puffer den Boundary-String entdecke,
        • schneide den Boundary-String vom /tmp-Handle ab
        • sichere die gewonnenen Daten in meinem Parser-Objekt
        • wiederhole den hier beschriebenen Algorithnmus solange, bis keine Daten mehr kommen.

        Okay, da haben wir doch was Greifbares zum drüber reden. Ich sehe da noch ziemlich viel Spiel für Verbesserungen, in absteigender Priorität:

        Zunächst hardcodest du die Grammatikregeln deines Parsers. Das führt zu verwobenen Code, über den nur mühevoll argumentiert werden kann. Das kannst du vermeiden, indem du die Grammatikregeln als Eingabe des Parsers auffasst anstatt sie fest im Quelltext zu verankern. Dann hast du einen abstrakten Parser, den du mit verschiedenen Grammatiken füttern kannst, und der dir als Ausgabe einen abstrakten Syntaxbaum liefert.

        Wie schon in meinem ersten Posting erwähnt, mischst du lexikalische und syntaktische Analyse miteinander. Dazu kann ich dir nur den selben Rat wieder erteilen: Benutze einen Tokenizer als vorbereitende Maßnahme für den Parser. In Kombination mit dem ersten Vorschlag, löst das auch gleich zwei deiner Probleme: Du musst dir keine Gedanken mehr darüber machen, mit wievielen Bindestrichen eine Boundary beginnt, und wie du an die Länge eines Blocks zwischen den Boundaries gelangst. Du kannst Nebensächlichkeiten ausblenden und das große Bild im Auge behalten.

        Außerdem haben Parser naturgemäß die Eigenheit, dass sie viele Fallunterscheidungen benötigen. Es ist eine süße Versuchung diese direkt mit verschachtelten if-Verzweigungen zu modellieren, allerdings wird das irgendwann unübersichtlich. Deshalb macht es Sinn diesem Aspekt mit einer strukturierten Lösung zu begegnen, endliche Automaten haben sich für kontextfreie Grammatiken als gutes Werkzeug etabliert.

        Die verschiedenen Phasen eines Parsers (lesen, lexikalische Analyse, syntaktische Analyse und Ausgabe schreiben) lassen sich zuletzt auch sehr gut parallelisieren. Deshalb bietet es sich an, die Komponenten mit zeitdiskreten Ein- und Ausgabeströmen zu verknüfen, anstatt mit punktuellen Daten. Das führt imho. zu besonders eleganten Interfaces.

        Zumindest die ersten drei Punkte sind ganz elementare Grundlagen bei der Entwicklung von Parsern, die in vielen Textbüchern sehr anschaulich behandelt werden.

        1. Zunächst hardcodest du die Grammatikregeln deines Parsers.

          So siehts aus, das Hardcoding hält sich in Grenzen.

              if($self->{CONTENT_TYPE} eq 'multipart/c-eav'){
                  require cEAV;
                  $self->{eav} = cEAV->decode_eav( $self->{rawdata} );
                  $self->{param} = $self->{eav}->{param};
              }
              elsif($self->{CONTENT_TYPE} eq 'multipart/form-data'){
                  $self->_parse_multipart; # factory
              }
              elsif( $self->{CONTENT_TYPE} eq 'application/json' ){
                  require JSON;
                  my $json = JSON->new;
                  $self->{json} = $json->decode($self->rawdata);
                  $self->{param} = $self->{json}{param};
              }
              else{
                  # ParameterNamen, application/x-www-form-urlencoded
                  my $pnames = [ $self->{rawdata} =~ /(\w+)=/g ];
                  foreach my $pname ( @$pnames ){
                      my $vals = [$self->{rawdata} =~ /$pname=([^;&]+)/g];
                      foreach my $v( @$vals){
                          $v =~ s/\+/ /g;
                          $v =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
                      }
                      $self->{param}{$pname} = $vals;
                  }
              }
          

          Die Scalierbarkeit und Übersichtlichkeit ist sichergestellt. Wenns zuviel wird, wirds in die Factory ausgelagert (Autoloader) . Falls ein neuer Content-Type hinzukommt, ist das ratzfatz eingebaut, da wird nur noch ne Methode aufgerufen.

          1. Der Codeausschnitt zeigt nichts von deinem Parser, sondern von einer Softwareschicht darüber, die deinen Parser lediglich benutzt.

            $self->_parse_multipart; # factory
            

            Darin verbirgt sich der für diesen Thread relevante Teil.

            PS: Ich kann kein perl, aber das sieht für mich danach aus, als würde diese Zeile eine Eigenschaft lesen und das Ergebnis direkt verwerfen, oder werden Methoden ohne Argumente tatsächlich in dieser Schreibweise aufgerufen?

            1. $self->_parse_multipart; # factory
              

              Darin verbirgt sich der für diesen Thread relevante Teil.

              PS: Ich kann kein perl, aber das sieht für mich danach aus, als würde diese Zeile eine Eigenschaft lesen und das Ergebnis direkt verwerfen, oder werden Methoden ohne Argumente tatsächlich in dieser Schreibweise aufgerufen?

              Ja, sorry, die Klammern kannste bei Perl-Methodenaufrufen weglassen.

        2. hallo nochmal,

          Wie schon in meinem ersten Posting erwähnt, mischst du lexikalische und syntaktische Analyse miteinander. Dazu kann ich dir nur den selben Rat wieder erteilen: Benutze einen Tokenizer als vorbereitende Maßnahme für den Parser.

          Tokenizer und Binärsequenzen, bring das besser nicht zusammen. Dass bei diesem Enctype ein Token (boundary_string) überhaupt funktioniert, begründet sich damit, dass die Wahrscheinlichkeit einer Kollision sehr gering ist.

          Trotz dieser geringen Wahrscheinlichkeit würde ich auch einen Enctype multipart/form-data wie andere Binärsequenzen grundsätzlich immer sequentiell parsen.

          Der von mir beschriebene Algorithmus funktioniert in FF, mit dem JS-FormData-Objekt (Ajax) und IE ausgezeichnet und ist plattformunabhängig. Mit Win32 und x86_64-linux am Server hab ich den getestet mit Perl und bei dieser Gelegenheit eine erstklassige Parser-Klasse für diesen Enctype erstellt. Multiple File-Upload Demo und Download des Perl-Moduls auf einer meiner Seiten...

          Abwärtskompatibel bis Perl v5.6.1 verzichtet mein Parser auf temporäre Dateien und bietet gegenüber CGI.pm so einige Vorteile, insbesondere eine einfachere Handhabung.

          Oh mann, jetzt ist mir dieser Form-Data-Schrott auch noch ans Herz gewachsen ;)

          Aber so umständlich, wie das ganze File-API, ArrayBuffer und Blob-Geraffel derzeit in modernen Browsern implementiert ist, bleib ich wahrscheinlich doch erstmal lieber bei FormData. pl

          1. Wie schon in meinem ersten Posting erwähnt, mischst du lexikalische und syntaktische Analyse miteinander. Dazu kann ich dir nur den selben Rat wieder erteilen: Benutze einen Tokenizer als vorbereitende Maßnahme für den Parser.

            Tokenizer und Binärsequenzen, bring das besser nicht zusammen. Dass bei diesem Enctype ein Token (boundary_string) überhaupt funktioniert, begründet sich damit, dass die Wahrscheinlichkeit einer Kollision sehr gering ist.

            Boundary ist einfach nur technisches Englisch für Trennsequenz. Die wird per Definition so gewählt, dass sie nicht in den Nutzdaten vorkommt.

            1. Wie schon in meinem ersten Posting erwähnt, mischst du lexikalische und syntaktische Analyse miteinander. Dazu kann ich dir nur den selben Rat wieder erteilen: Benutze einen Tokenizer als vorbereitende Maßnahme für den Parser.

              Tokenizer und Binärsequenzen, bring das besser nicht zusammen. Dass bei diesem Enctype ein Token (boundary_string) überhaupt funktioniert, begründet sich damit, dass die Wahrscheinlichkeit einer Kollision sehr gering ist.

              Boundary ist einfach nur technisches Englisch für Trennsequenz. Die wird per Definition so gewählt, dass sie nicht in den Nutzdaten vorkommt.

              Es gibt noch eine Trennsequenz bei desem Enctype, das ist die Leerzeile CRLFCRLF. Und die Wahrscheinlichkeit, dass CRLFCRLF in beliebigen Binaries (auch Textdateien sind Binaries) vorkommen kann, ist sehr groß.

              Idealerweise, würde es für jeden Part eine Content-Length Angabe geben, gäbe es überhaupt keine Kollisionen und der Parser wäre deutlich performanter, weil das byteweise Lesen durch blockweise Lesen ersetzt werden kann. pl

              PS: Noch besser wäre eine binäre Serialisierung, mit modernen Browsern ist das möglich. Da wird nur noch mit Längenangaben operiert, aber wie gesagt: ArrayBuffer-Geraffel ;)

              1. Tach,

                Es gibt noch eine Trennsequenz bei desem Enctype, das ist die Leerzeile CRLFCRLF.

                nicht bei multipart/form-data.

                mfg
                Woodfighter

                1. Tach,

                  Es gibt noch eine Trennsequenz bei desem Enctype, das ist die Leerzeile CRLFCRLF.

                  nicht bei multipart/form-data.

                  Ja doch, selbstverständlich gibt es die Leerzeile (CRLFCRLF) da, die ist vor der Binary eingefügt.

                  1. Tach,

                    Es gibt noch eine Trennsequenz bei desem Enctype, das ist die Leerzeile CRLFCRLF.

                    nicht bei multipart/form-data.

                    Ja doch, selbstverständlich gibt es die Leerzeile (CRLFCRLF) da, die ist vor der Binary eingefügt.

                    das ist doch alles Teil der selben Boundary und es müssen keine zwei Zeilenumbrüche kommen: „ The boundary delimiter MUST occur at the beginning of a line, i.e., following a CRLF, and the initial CRLF is considered to be attached to the boundary delimiter line rather than part of the preceding part. The boundary may be followed by zero or more characters of linear whitespace. It is then terminated by either another CRLF and the header fields for the next part, or by two CRLFs, in which case there are no header fields for the next part.“ – https://www.ietf.org/rfc/rfc2046.txt

                    mfg
                    Woodfighter

              2. Es gibt noch eine Trennsequenz bei desem Enctype, das ist die Leerzeile CRLFCRLF.

                Ja, um Kopfdaten von Nutzdaten zu trennen.

                Und die Wahrscheinlichkeit, dass CRLFCRLF in beliebigen Binaries (auch Textdateien sind Binaries) vorkommen kann, ist sehr groß.

                Und das stellt auch kein Problem dar. Aus dem Kontext heraus ist der Zweck des doppelten Zeilenumbruchs klar: Der erste doppelte Zeilenumbruch nach den Kopfdaten markiert den Anfang der Nutzdaten. Weitere doppelte Zeilenumbrüche sind einfache Nutzdaten - Verwechslungen ausgeschlossen.

                Du stellst dich vor Probleme, die es nicht gibt. Das zeigt mir auch dein ständiges Beharren auf Binärsequenzen, als seien sie so fundamental unterschiedlich von Strings, dass sie eine ständige Extrawurst spielen. Das ist nicht der Fall, und das hält dich auf. In Wahrheit macht eine Unterscheidung in den seltensten Fällen überhaupt Sinn. Versuche dich mal von deinem bisherigen Denkansazt zu lösen und fasse Bytes und Zeichen einfach als Symbole auf, dann sind Strings und Binärsequenzen einfach nur noch die Aneinanderreihung von Symbolen. Wenn du diese Abstraktion verinnerlichst, wirst du in der Lage sein, künftig Operationen des einen Datentyps auf den jeweils anderen zu übertragen. Ich möchte dir sehr an Herz legen ein Mal ein Anfängerbuch über formale Sprachen zu lesen, das wird deinem technischen Verständnis von enormen Nutzen sein.

                1. Es gibt noch eine Trennsequenz bei desem Enctype, das ist die Leerzeile CRLFCRLF.

                  Ja, um Kopfdaten von Nutzdaten zu trennen.

                  Und die Wahrscheinlichkeit, dass CRLFCRLF in beliebigen Binaries (auch Textdateien sind Binaries) vorkommen kann, ist sehr groß.

                  Und das stellt auch kein Problem dar. Aus dem Kontext heraus ist der Zweck des doppelten Zeilenumbruchs klar: Der erste doppelte Zeilenumbruch nach den Kopfdaten markiert den Anfang der Nutzdaten. Weitere doppelte Zeilenumbrüche sind einfache Nutzdaten - Verwechslungen ausgeschlossen.

                  Ganz genau! Genau deswegen wird das Teil ja auch sequentiell zerlegt ;)

                  Du stellst dich vor Probleme, die es nicht gibt. Das zeigt mir auch dein ständiges Beharren auf Binärsequenzen, als seien sie so fundamental unterschiedlich von Strings, dass sie eine ständige Extrawurst spielen.

                  Da hast Du was an meiner, hauptsächlich durch Perl geprägten Denkweise falsch verstanden ;)

                  In Perl sind das alles nur Bytesequenzen und Perl arbeitet im Default byteorientiert. Erst seit Version v5.8 wird zwischen Strings und Bytesequenzen unterschieden, aber auch nur dann, wenn das der Programmierer explizit so will.

                  Der Dateibegriff ist übrigens geprägt duch Niklas Wirth: Dateien sind Sequenzen (um 1980). In seinen Büchern beschriebene Algorithmen und Grundlagen haben auch heute noch ihre Gültigkeit. pl

                  1. Ganz genau! Genau deswegen wird das Teil ja auch sequentiell zerlegt ;)

                    Ich rekapituliere nochmal, wie wir zu diesem Punkt in unserem Gespräch gelangt sind: Ich habe dir empfohlen die lexikalische Analyse deiner Eingabedaten mit einem Tokenizer durchzführen. Daraufhin glaubtest du, dass das mit Binärdaten nicht funktionieren könne. Deine Argumentation stützt sich dabei darauf, dass Tokenizer wohl keine Trennseqzenzen lesen können.

                    Die einzige Aufgabe eines Tokenizers ist das Zerlegen einer zusammenhängenden Eingabe in logisch getrennte Einheiten, sogenannte Token. Das Erkennen von Trennsymbolen ist die Kernaufgabe eines Tokenizers.

                    Du stellst dich vor Probleme, die es nicht gibt. Das zeigt mir auch dein ständiges Beharren auf Binärsequenzen, als seien sie so fundamental unterschiedlich von Strings, dass sie eine ständige Extrawurst spielen.

                    Da hast Du was an meiner, hauptsächlich durch Perl geprägten Denkweise falsch verstanden ;)

                    Das gestehe ich dir ja zu. Trotzdem lässt du dich dadurch häufig zu Fehlannahmen und Fehlschlüssen verleiten, sowie auch wieder in diesem Thread geschehen. Das demonstriert eben, dass du wichtige Grundlagen im Umgang mit formalen Sprachen nicht beherrschst, deswegen mein Rat zu einem Einsteigerbuch zu dem Thema.

                    Der Dateibegriff

                    Wieso machst du jetzt einen Transfer zu Dateien?

                    1. Ganz genau! Genau deswegen wird das Teil ja auch sequentiell zerlegt ;)

                      Ich rekapituliere nochmal, wie wir zu diesem Punkt in unserem Gespräch gelangt sind: Ich habe dir empfohlen die lexikalische Analyse deiner Eingabedaten mit einem Tokenizer durchzführen.

                      Nochmal: Dateien sind Sequenzen, sie werden sequentiell erzeugt und sequentiell gelesen. Ein Token ist letztendlich auch nur eine Sequenz.

                      Daraufhin glaubtest du, dass das mit Binärdaten nicht funktionieren könne.

                      Es kann schiefgehen. Besser als ein Tokenizer ist ein sequentielles Parsen auf Byte-Ebene. Vorausgesetzt, die Datei wurde zweckmäßig erstellt, was bei multipart/form-data nicht unbedingt zutrifft aufrund fehlender Längenangaben (siehe Eröffnungspost).

                      Trotz möglicher Kollisionen halte ich es nach Niklas Wirth und parse einen multipart/form-data sequentiell, das hat sich auch in CGI.pm bewährt was noch viel älter ist als meine eigenen Entwicklungen.

                      Wenn Du einen multipart/form-data unbedingt mit einem Tokenizer parsen möchtest, bitteschön, mach es einfach.

                      Und noch was: multipart/form-data ist im Grunde genommen Schrott. Es gibt Serialize-Algorithmen die zweckmäßiger sind und auch das Wiederherstellen der Daten wesentlich vereinfachen würden.

                      1. Hallo pl,

                        eigentlich wollte ich dir nicht mehr antworten, aber… meh.

                        Nochmal: Dateien sind Sequenzen, sie werden sequentiell erzeugt und sequentiell gelesen.

                        Das ist nicht richtig. Sie können sequentiell gelesen oder geschrieben werden. Random-Zugriff ist allerdings auch möglich, siehe lseek(2)/fseek(3) und Konsorten. Genau genommen liegt übrigens eine Datei oft gar nicht wirklich sequentiell vor, die Inhalte von Dateien können sich an beinahe beliegen Orten der Festplatte befinden (Stichwort Fragmentierung). Das Konstrukt Datei ist ein rein logisches und hat nichts mit sequentiellem/zufälligem Zugriff zu tun.

                        Ein Token ist letztendlich auch nur eine Sequenz.

                        Ein Token ist die kleinste sinngebende Einheit in einer Grammatik bzw formalen Sprache. Was du hier mit Sequenz (lt Wikipedia Reihenfolge, Ordung) meinst ist nicht klar.

                        Daraufhin glaubtest du, dass das mit Binärdaten nicht funktionieren könne.

                        Es kann schiefgehen. Besser als ein Tokenizer ist ein sequentielles Parsen auf Byte-Ebene.

                        Das, was du als „sequentielles Parsen auf Byte-Ebene“ bezeichnest, ist Teil der lexikalischen Analyse eines Tokenizers. Der macht (stark vereinfacht) nichts anderes als deinen Eingabestrom durchzugehen und dort Zeichen für Zeichen solange das Wort (Token) zusammenzubauen, bis er ein vollständiges Wort (Token) erhalten hat. Das gibt er dann weiter.

                        Das, was 1UnitedPower dir vorgeschlagen hat, ist die Unterteilung der logischen Einheiten deines Parsers in lexikalische Analyse (Tokenizer) und semantische Analyse (Parser). Das daraus resultierende Konstrukt ist besser wartbar, einfacher erweiterbar und nicht so ein monolithyscher Block wie eine Vermischung der beiden Phasen.

                        Dieses Prinzip beschreibt Wirth übrigens in seinen Lehrbüchern über formale Sprachen und Compilerbau.

                        Vorausgesetzt, die Datei wurde zweckmäßig erstellt, was bei multipart/form-data nicht unbedingt zutrifft aufrund fehlender Längenangaben (siehe Eröffnungspost).

                        Da du eh auf einem Eingabestrom arbeitest, sind deine Längenangaben gar nicht notwendig. Im wesentlichen muss dein Parser eine Finite State Machine nachbauen, die bei autreten bestimmter Tokens den Status wechselt. Das Boundary, an dem du dich so gestossen hast, muss so gewählt werden, dass keine Kollision mit dem Inhalt stattfinden kann.

                        Trotz möglicher Kollisionen halte ich es nach Niklas Wirth und parse einen multipart/form-data sequentiell, das hat sich auch in CGI.pm bewährt was noch viel älter ist als meine eigenen Entwicklungen.

                        Wenn Du einen multipart/form-data unbedingt mit einem Tokenizer parsen möchtest, bitteschön, mach es einfach.

                        Ich glaube, dir fehlt es an Verständnis, was ein Tokenizer ist und was er tut. Ob das Parsing sequentiell ist oder nicht, das hat überhaupt nichts mit dem Einsatz eines Tokenizers zu tun. Ein kleines Beispiel: wir betrachten eine einfache formale Sprache L mit den Symbolen +, -, * und / sowie den Ziffern 0 bis 9. Gegeben ist der Eingabestring 1 + 2 * 3 - 4. Der Tokenizer würde jetzt diesen Eingabestring in die einzelnen Symbole 1, +, 2, *, 3, - und 4 zerlegen. Der darauf folgende Parser könnte, ohne sich um die lexikalische Analyse kümmern zu müssen, daraus z.B. einen AST bauen:

                           -
                        4      +
                            1    *
                               2   3
                        

                        Das Konzept der Unterteilung in lexikalische Analyse (Tokenizer) und semantische Analyse (Parser) hat also überhaupt nichts mit sequentiell oder nicht-sequentiell zu tun.

                        Ich kann dir zu diesem Thema das Dragon Book (Compilers: Principles, Techniques and Tools) empfehlen, das Standard-Werk zu diesem Thema. Alternativ auch Compiler Construction von Wirth, wobei ich mit seinem Schreibstil immer Mühe hatte.

                        LG,
                        CK

                        Edit: peinlichen Fehler im AST gefixed ;-)

                2. Oh, Oh,

                  ...Fall, und das hält dich auf. In Wahrheit macht eine Unterscheidung in den seltensten Fällen überhaupt Sinn. Versuche dich mal von deinem bisherigen Denkansazt zu lösen und fasse Bytes und Zeichen einfach als Symbole auf,

                  Bloß nicht! Der Symbolbegriff hat eine völlig andere Bedeutung beim Programmieren mit Perl!