pl: c binary mode für stdin

068

c binary mode für stdin

  1. 0
    1. 0
    2. 0
      1. 0
        1. 0
          1. 0
        2. 0
          1. 0
            1. 0
              1. 0
                1. 0
      2. 0

        multipart/form-data

        1. 0
          1. 0
            1. 0
            2. 0
              1. 0
                1. 0
        2. 0
        3. 0
          1. 0
            1. 0
              1. 0
          2. 0
            1. 0
              1. 0
              2. 0
                1. 0
                  1. 0
                2. 0
                  1. 0
                    1. 0
                      1. 0
                        1. 0
                          1. -1
                            1. 0
                        2. 0
                3. 2
                  1. 0
                  2. 0
                  3. 0
                    1. 0
                      1. 0
                        1. 0
                        2. 2
                          1. 1
                            1. 0
                    2. 0
                      1. 0
                4. 0
  2. 0

    C Upload goßer Datenmengen

    1. 0
      1. 0
        1. 0
          1. 0
            1. 0
              1. 0
              2. 0

                Bug in PHP PHP/5.3.0 Upload

                1. 0
                  1. 0
                    1. 0
                      1. 0
                        1. 0
            2. 0
            3. 0
    2. 2
      1. 4

s. Thema.

In C aus stdin lesen ist per Voreinstellung zeichenorientiert. Ein Umschalten auf binmode gänge so:

freopen(0, "rb", stdin);

nur ist das auf einem WinOS leider ohne Effekt. Wenn ich das so anweise erscheint stdin als ausgelesen und da gibts bekanntlich kein Zurück 😉 (aber auch keine Fehlermeldungs)

Idee?

  1. Hallo pl,

    setmode()?

    Rolf

    -- sumpsi - posui - clusi
    1. problematische Seite

      hi @Rolf B

      setmode()?

      setmode(STDIN_FILENO, O_BINARY); und der Tag ist gerettet 😉

      Herzlichen Dank, funktioniert einwandfrei!

    2. hi @Rolf B

      nochemal danke! Die Uploads mit meinem neuen Enctype binary/name+value funktionieren einwandfrei!

      Der Trick besteht darin beim Einlesen der Parameterliste die Längenangabe mitzunehmen, das sieht dann so aus:

      /* Speicher anfordern, Längenangaben liegen vor */ new->name = (char*)malloc(alen+1); new->value = (char*)malloc(vlen+1); fread(new->name,alen,1,stdin); fread(new->value,vlen,1,stdin); // Terminieren und speichern new->name[alen] = 0; new->value[vlen] = 0; new->vlen = vlen; new->alen = alen; new->next = NULL; und das struct // Parameter aus stdin oder QUERY_STRING // werden auf diese Liste gelesen struct PARAM{ unsigned int vlen; unsigned int alen; char *name; char *value; struct PARAM *next; }; typedef struct PARAM PARAM;

      Es steht nun frei den Wert eines Parameters verwenden, entweder als String oder als ArrayBuffer die Bytes auszulesen und damit die Binary.

      So einfach kann das manchmal sein, und das muss es auch 😉

      MfG

      1. Moin pl,

        ich habe ein paar Fragen zu deinem Code:

        Der Trick besteht darin beim Einlesen der Parameterliste die Längenangabe mitzunehmen, das sieht dann so aus:

        /* Speicher anfordern, Längenangaben liegen vor */ new->name = (char*)malloc(alen+1); new->value = (char*)malloc(vlen+1);

        Wenn ich mit die Definition der struct unten anschaue, dann wäre es doch sinnvoll, deren Attribute alen und vlen zu nutzen und nicht (globale?) Variablen, also

        new->vlen = vlen; new->alen = alen; new->name = (char*)malloc(new->alen+1); new->value = (char*)malloc(new->vlen+1);

        Zudem beachte, dass eine Variable mit dem Namen new nicht zu C++ kompatibel ist, da new dort ein reserviertes Wort ist.

        Und du kannst hier noch etwas „Platz sparen“, indem du die struct direkt als Typ definierst:

        typedef struct { size_t vlen; size_t alen; char *name; char *value; PARAM *next; } PARAM;

        Ich habe zudem unsigned int durch size_t ersetzt, denn size_t ist groß genug um die Speichermenge für malloc zu enthalten. Auf einem 64-Bit-System ist

        sizeof(unsigned int) < sizeof(size_t) /* 4 Bit 8 Bit */

        während auf einem 32-Bit-System

        sizeof(unsigned int) == sizeof(size_t) /* 4 Bit 4 Bit */

        Viele Grüße
        Robert

        1. Hi,

          Auf einem 64-Bit-System ist

          sizeof(unsigned int) < sizeof(size_t) /* 4 Bit 8 Bit */

          während auf einem 32-Bit-System

          sizeof(unsigned int) == sizeof(size_t) /* 4 Bit 4 Bit */

          Bitte Byte, nicht Bit, in den Kommentaren …

          cu,
          Andreas a/k/a MudGuard

          1. Moin @@MudGuard,

            stimmt natürlich, Danke!

            Viele Grüße
            Robert

        2. hi @Robert B.

          ich habe ein paar Fragen zu deinem Code:

          Der Trick besteht darin beim Einlesen der Parameterliste die Längenangabe mitzunehmen, das sieht dann so aus:

          /* Speicher anfordern, Längenangaben liegen vor */ new->name = (char*)malloc(alen+1); new->value = (char*)malloc(vlen+1);

          Wenn ich mit die Definition der struct unten anschaue, dann wäre es doch sinnvoll, deren Attribute alen und vlen zu nutzen und nicht (globale?) Variablen, also

          Meine Liste PARAM ist nicht global, sie existiert nur im scope der main, da wird der Pointer ins Leben gerufen und an die readParam() übergeben.

          Ich habe zudem unsigned int durch size_t ersetzt, denn size_t ist groß genug um die Speichermenge für malloc zu enthalten. Auf einem 64-Bit-System ist…

          Das wäre aber falsch. vlen ist definitiv die Anzahl der Bytes, die ist von der Architektur unabhängig. Was ich jedoch geändert habe:

          struct PARAM{ unsigned int vlen; unsigned int alen; char *name; unsigned char *value; struct PARAM *next; };

          denn die Oktettenwertigkeiten sind ja alle positiv. Sonst würde aus 0xFF eine -1 und das irritiert beim Debuggen. Funktional ists jeoch egal ob unsigned oder nicht.

          Ansonsten ist vlen+1 schon tricky: Der Platz für die 0 zum Termineren ist reserviert, und vlen selbst ist die exakte Länge der Binary (falls das ein Input type file ist). Bei mehreren gleichnamigen inputs gibts natürlich auch für Uploads genausoviele name+value Pärchen.. alles brav im Hauptspeicher für den wahlfeien Zugriff 😉

          Und vielen Dank für Deine Hinweise!

          PS: Interessanterweise nimmt jQuery(form).serializeArray() bei der <textarea> den Zeilenumbruch als 0D 0A also 2 Zeichen. Mein Serializer liest da nur ein Zeichen.

          1. Moin pl,

            Ich habe zudem unsigned int durch size_t ersetzt, denn size_t ist groß genug um die Speichermenge für malloc zu enthalten. Auf einem 64-Bit-System ist…

            Das wäre aber falsch. vlen ist definitiv die Anzahl der Bytes, die ist von der Architektur unabhängig.

            Ein size_t ist genau so groß wie ihn malloc benötigt, schau dir die Deklaration davon an:

            void* malloc( size_t size );

            Ein unsigned int ist allerdings von der Architektur abhängig. Der C-Standard garantiert dir lediglich, dass folgende Gleichungen erfüllt sind:

            sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long) sizeof(char) < sizeof(long)

            Wenn du wohl definiert große Datentypen haben möchtest, gibt es noch die int*_t- sowie uint*_t-Typen aus dem Header stdint.h:

            • mit Vorzeichen: int8_t, int16_t, int32_t und int64_t
            • ohne Vorzeichen: uint8_t, uint16_t, uint32_t und uint64_t

            Viele Grüße
            Robert

            1. hi @Robert B.

              ja der Datentyp ist wichtig für die Längenangaben in der Binary (uint32_t Little Endian, 4 Bytes). Hier mal der ganze Code:

              int readParam(struct PARAM **par){ int done = 0; setmode(STDIN_FILENO, O_BINARY); if( getenv("CONTENT_TYPE") && 0 == strcmp("binary/name+value", getenv("CONTENT_TYPE") ) ){ int content_length = 0; if( getenv("CONTENT_LENGTH") ) content_length = atoi(getenv("CONTENT_LENGTH")); if( ! content_length ) return 0; int amt = 0; /* Puffer für die zwei Längenangaben */ uint32_t lens[2]; unsigned int alen, vlen; while( fread(lens,sizeof lens,1,stdin) ){ struct PARAM *new = NULL; new = malloc(sizeof *new); alen = htonl(lens[0]); // name vlen = htonl(lens[1]); // value amt = amt + alen + vlen; if( amt > content_length ) return 0; if( amt < 0 ) return 0; /* Speicher anfordern */ new->name = (char*)malloc(alen+1); new->value = (char*)malloc(vlen+1); fread(new->name,alen,1,stdin); fread(new->value,vlen,1,stdin); new->name[alen] = 0; new->value[vlen] = 0; new->vlen = vlen; new->alen = alen; new->next = NULL; while( *par != NULL ){ par = &(*par)->next; } *par = new; done++; } return done; } else{ //application/x-www-form-urlencoded, Default Enctype // dasselbe struct..

              Änderung vorbehalten 😉 Und danke für weitere Hinweise.

              MfG

              PS: Daß es Little Endians (N-Order) sind hat sich geschichtlich ergeben. Vax-Order hätte den Vorteil daß sie nicht umgerechnet werden muss. Aus Kompatibilitäsgründen zu Perl und PH muss ich jedoch bei der N-Order bleiben, jedenfalls privat. Ansonsten müsste ich einige Libraries umschreiben.

              1. Moin pl,

                ja der Datentyp ist wichtig für die Längenangaben in der Binary (uint32_t Little Endian, 4 Bytes).

                OK, das ist in deinem „Protokoll“ so definiert, dann ist es natürlich notwendig, die richtige Größe zu verwenden.

                Hier mal der ganze Code:

                … mit hoffentlich nützlichen Anmerkungen und Anregungen:

                int readParam(struct PARAM **par){ int done = 0; setmode(STDIN_FILENO, O_BINARY);

                Ich habe nicht herausgefunden, wo diese Funktion setmode herkommt, aber sie wird mit Sicherheit einen Rückgabewert haben, der erkennen lässt, ob ihr Aufruf erfolgreich war und was mit dem Stream los ist. Dieser Rückgabewert sollte daher geprüft und entsprechend behandelt werden.

                if( getenv("CONTENT_TYPE") && 0 == strcmp("binary/name+value", getenv("CONTENT_TYPE") ) ){ int content_length = 0; if( getenv("CONTENT_LENGTH") ) content_length = atoi(getenv("CONTENT_LENGTH")); if( ! content_length ) return 0; int amt = 0;
                • Du kannst dir einen der jeweils beiden Aufrufe von getenv sparen, indem du die Rückgabewerte in Variablen speicherst. Je nach Implementierung durchläuft getenv möglicherweise jedes Mal die Liste der Umgebungsvariablen:
                  char *cType = getenv("CONTENT_TYPE");
                  char *cLength = getenv("CONTENT_LENGTH");
                • strcmp arbeitet ohne Längenprüfung, evtl. ist strncmp sicherer.
                • Die Variablen content_length und amt sollten nach meinem Dafürhalten vom gleichen Typ wie alen und vlen sein, denn sonst kann im Folgenden amt überlaufen. Kann content_length überhaupt negativ sein?
                • atoi signalisiert keinen Fehler beim Konvertieren nach int, verwende besser strtol oder strtoul.
                /* Puffer für die zwei Längenangaben */ uint32_t lens[2]; unsigned int alen, vlen; while( fread(lens,sizeof lens,1,stdin) ){ struct PARAM *new = NULL; new = malloc(sizeof *new);
                • Du kannst new direkt mit dem Speicher von malloc initialisieren:
                  struct PARAM *new = (struct PARAM*) malloc(sizeof(*new));
                • Und natürlich muss der Rückgabewert von malloc geprüft werden.
                alen = htonl(lens[0]); // name vlen = htonl(lens[1]); // value amt = amt + alen + vlen;
                • Das geht etwas kürzer: amt += alen + vlen.
                • Hier kann es zum Integer-Überlauf kommen, da alen und vlen unsigned sind und amt nur int.
                if( amt > content_length ) return 0; if( amt < 0 ) return 0;

                Hier müssen dann natürlich amt und content_length den gleichen Datentyp haben.

                /* Speicher anfordern */ new->name = (char*)malloc(alen+1); new->value = (char*)malloc(vlen+1);

                Die Rückgabewerte von malloc werden nicht überprüft.

                fread(new->name,alen,1,stdin); fread(new->value,vlen,1,stdin);

                Die Rückgabewerte von fread werden nicht überprüft.

                new->name[alen] = 0; new->value[vlen] = 0; new->vlen = vlen; new->alen = alen; new->next = NULL; while( *par != NULL ){ par = &(*par)->next; } *par = new; done++; } return done; } else{ //application/x-www-form-urlencoded, Default Enctype // dasselbe struct..

                Viele Grüße
                Robert

                Folgende Nachrichten verweisen auf diesen Beitrag:

                1. Danke Dir!

                  PS: Max CONTENT_LENGTH wird ohnehin auf einen endlichen Wert festgelegt der weit unter uint32_t liegt.

      2. hi @Rolf B

        nochemal danke! Die Uploads mit meinem neuen Enctype binary/name+value funktionieren einwandfrei!

        Und nun auch mit Enctype multipart/form-data -- den Parser hierfür habe ich auch fertig in C. Und es zeigte sich auch beim Entwickeln des Parsers in C für diesen Enctype der Designfehler dessen der darin besteht, daß es keine Längenangabe für die value-Parts gibt.

        So muß man den Speicherbereich großzügig anfordern, weil man gar nicht weiß wieviel man benötigt. Nun mangelts heutzutage zwar nicht an RAM aber als dieser Enctype verabschiedet wurde schon.

        Vielleicht hat ja jemand noch eine andere Idee, als diesen Enctype byte für byte aus stdin zu lesen. Vielleicht geht ja was mit memcpy, memset & Co.

        Im Übrigen ist die boundary überflüssig.

        MfG

        PS: multipart/form-data ist Schrott, wer einmal gesehen hat wie Uploads mit binary/name+value abgehen, wird nie wieder was mit multipart/form-data machen wollen!

        1. Tach!

          Und es zeigte sich auch beim Entwickeln des Parsers in C für diesen Enctype der Designfehler dessen der darin besteht, daß es keine Längenangabe für die value-Parts gibt.

          Wenn das ein Designfehler sein soll, so sind auch alle anderen Datenformate ein Designfehler, die keine vorangestellte Längenangabe haben. Und das sind eine ganze Menge. Das Problem ist ähnlich gelagert zur Verarbeitung großer Datenmengen bei begrenztem Speicher oder der absichtlich Verwendung nur wenig Speichers. Man erstellt einen Puffer einer definierten Größe und liest die Daten in diesen ein, bis entweder der Puffer voll ist oder die Quelle keine weiteren Daten mehr liefert. Dann bearbeitet man den Puffer und fährt anschließend gegebenenfalls mit dem weiteren Einlesen fort. Da das sehr häufig vorkommt, sollte es dazu genügend Informationen zu empfohlenen Vorgehensweisen geben.

          dedlfix.

          1. Tach!

            Und es zeigte sich auch beim Entwickeln des Parsers in C für diesen Enctype der Designfehler dessen der darin besteht, daß es keine Längenangabe für die value-Parts gibt.

            Wenn das ein Designfehler sein soll, so sind auch alle anderen Datenformate ein Designfehler, die keine vorangestellte Längenangabe haben.

            Eine solche Verallgemeinerung ist Unsinn! Aber was den Enctype multipart/form-data betrifft, ist es ein Designfehler der nicht gerade trivial ist. So muss man die gesamte Binary in Schritten von 1 byte lesen, was sich negativ auf die Performance auswirkt.

            Am Binary Part angekommen (nach jeder Leerzeile), wird man einen Puffer mit der Größe CONTENT_LENGTH anlegen müssen damit kein Pufferüberlauf entsteht. Da CONTENT_LENGTH die Länge des gesamten POST ist, führt das dazu, daß, je nach Anzahl der Parts, der Speicherbedarf ein Vielfaches von CONTENT_LENGTH betragen kann. Mann könnte den Puffer um die Länge der Boundary kürzer anlegen aber das sind nur um die 40..50 Bytes.

            So wird die Binary dann Byte für Byte in den Puffer gelesen solange bis die Boundary am Ende dieses Puffers erscheint. Das heißt, man muss das nach jedem Byte prüfen, was auch wieder very schlecht für die Performanze ist.

            Und schließlich muss man die Boundary am Ende des Puffers wieder abschneiden.

            Hätte man für jeden Part eine Längenangabe, wäre der Algorithmus viel einfacher, performanter, CPU und RAM gefälliger.

            Beim Default Enctype ergibt sich ein ähnliches Bild. Insbesondere die Prozentkodierung wirkt sich bei großen Datenmengen schlecht auf die Performance aus.

            Und ja, da hast Du Recht, XML, CSV, JSON u.a. Enctypes sind allesamt ineffizient für den Datentransport. Man kann nämlich jeden sequentiellen Serializealgorithmus in jeder Programmiersprache implementieren: Maschinenlesbar, Effizient und Plattformübergreifend!

            Diese Aussage kann jeder bestätigen der mal selbst ein paar Serializer entwickelt hat die sequentiell arbeiten. Meine Serializer für verschiedene Enctypes (Content-Type) funktionieren übrigens in JavaScript, PHP, Perl und in C gleichermaßen.

            Und was wäre ein FW, wenn der Parser nicht erweiterbar wäre um weitere Enctypes!

            MfG

            PS: multipart/form-data ist von allen anderen Enctypes der Grottigste!

            1. Hallo pl,

              Diese Aussage kann jeder bestätigen der mal selbst ein paar Serializer entwickelt hat die sequentiell arbeiten. Meine Serializer für verschiedene Enctypes (Content-Type) funktionieren übrigens in JavaScript, PHP, Perl und in C gleichermaßen.

              Und was wäre ein FW, wenn der Parser nicht erweiterbar wäre um weitere Enctypes!

              PS: multipart/form-data ist von allen anderen Enctypes der Grottigste!

              Hat ja nicht lange angehalten.

              Bis demnächst
              Matthias

              -- Pantoffeltierchen haben keine Hobbys.
            2. Tach!

              Und ja, da hast Du Recht, XML, CSV, JSON u.a. Enctypes sind allesamt ineffizient für den Datentransport. Man kann nämlich jeden sequentiellen Serializealgorithmus in jeder Programmiersprache implementieren: Maschinenlesbar, Effizient und Plattformübergreifend!

              Kann man. Man hat sich aber dafür entschieden, diese Formate auch menschenlesbar und vor allem ohne großartige Hilfsmittel schreibbar zu gestalten. Plattformübergreifend!

              dedlfix.

              1. Tach!

                Und ja, da hast Du Recht, XML, CSV, JSON u.a. Enctypes sind allesamt ineffizient für den Datentransport. Man kann nämlich jeden sequentiellen Serializealgorithmus in jeder Programmiersprache implementieren: Maschinenlesbar, Effizient und Plattformübergreifend!

                Kann man. Man hat sich aber dafür entschieden, diese Formate auch menschenlesbar und vor allem ohne großartige Hilfsmittel schreibbar zu gestalten. Plattformübergreifend!

                multipart/form-data menschenlesbar ist ja wohl ein Witz! Und auch die Frage wozu!

                Und wie gesagt, entwickle mal selbst!

                Hast Du überhaupt schonmal einen Parser für multipart/form-data entwickelt!? Mach das mal, mit dieser Erfahrung siehst Du solche Dinge nicht mehr theoretisch sondern praktisch und vor allem realistischer!

                Auf die Menschenlesbarkeit kommt es in MediaTypes für moderne Anwendungen schonmal gar nicht an. Vielmehr kommt es darauf an, daß image/gif, audio/mp3 usw. auf verschiedenen Plattformen erzeugt und wiedergegeben sowie transportiert werden können und nicht ob diese Dateien für den Menschen lesbar sind!

                MfG

                1. Gibt Dir Dein überhebliches, selbstgefälliges Geschwurbel eigentlich irgendwas?

        2. noch ein paar Tests zu multipart/form-data: Lokal dauert die Verarbeitung eines 11 MB Uploads (4 Dateien) mit meinem Parser in C 6 Sekunden.

          Perl zerlegt denselben POST dreimal schneller, allerdings geht mein Perlparser nicht byteweise durch sondern splittet an der Boundary.

          Trotzdem erscheinen mir 6 Sekunden für ein C Programm etwas zuviel. Hat da mal jemand was Vergleichbares (Lokale Testumgebung, Browser und Server auf Localhost)?

          MfG

          PS: Perl hat mit demselben Algorithmus fast 4 Minuten gebraucht, 4 Dateien 11 MB.

        3. Hallo pl,

          Und nun auch mit Enctype multipart/form-data -- den Parser hierfür habe ich auch fertig in C. Und es zeigte sich auch beim Entwickeln des Parsers in C für diesen Enctype der Designfehler dessen der darin besteht, daß es keine Längenangabe für die value-Parts gibt.

          Aber es gibt eine Längenangabe für die kompletten Formulardaten, zur Verfügung gestellt in der Umgebungsvariablen CONTENT_LENGTH.

          So muß man den Speicherbereich großzügig anfordern, weil man gar nicht weiß wieviel man benötigt. Nun mangelts heutzutage zwar nicht an RAM aber als dieser Enctype verabschiedet wurde schon.

          Nun ja, je nach Anwendungsfall kann es reichen, den benötigten Speicher nach Konsultation von CONTENT_LENGTH einmal anzufordern und dann nur noch Pointer auf das jeweilige Byte nach der Boundary vorzuhalten, zusammen mit der Länge bis zur nächsten Boundary.

          Vielleicht hat ja jemand noch eine andere Idee, als diesen Enctype byte für byte aus stdin zu lesen. Vielleicht geht ja was mit memcpy, memset & Co.

          Im Übrigen ist die boundary überflüssig.

          Weil?

          PS: multipart/form-data ist Schrott, wer einmal gesehen hat wie Uploads mit binary/name+value abgehen, wird nie wieder was mit multipart/form-data machen wollen!

          Wenn ich ein klassisches HTML <form> verwende, bekomme ich mit mit deinem binary/name+value nichts hochgeladen:

          <form action="/cgi-bin/form.pl" method="post" enctype="binary/name+value"> <p><label>Einzeiler: <input type="text" name="eins" size="25"/></label></p> <p><label>Mehrzeiler: <textarea name="mehr" cols="50" rows="5"></textarea></p> <p><label>Textdatei: <input type="file" name="datei" size="25" accept="text/*"/></label></p> <button type="submit">Absenden</button> <button type="reset">Zurücksetzen</button> </form>

          Der Code in form.pl:

          #!/usr/bin/perl -Tw use strict; print "Content-Type: text/plain; charset=utf-8\r\n\r\n"; while (my ($k, $v) = each %ENV) { print $k, "\t", $v, "\n" } my $len = $ENV{'CONTENT_LENGTH'}; if ($len) { print "\nUploaded ", $len, " bytes:\n"; my $data; if (read STDIN, $data, $len) { print $data } }

          Ergibt beim Hochladen u.a.:

          HTTP_ACCEPT_ENCODING gzip, deflate HTTP_ACCEPT text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 REQUEST_URI /cgi-bin/form.pl SERVER_PROTOCOL HTTP/1.1 HTTP_ACCEPT_LANGUAGE de-DE,de;q=0.8,en-US;q=0.5,en;q=0.3 CONTEXT_PREFIX /cgi-bin/ HTTP_UPGRADE_INSECURE_REQUESTS 1 HTTP_USER_AGENT Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0 SCRIPT_URL /cgi-bin/form.pl CONTENT_TYPE application/x-www-form-urlencoded QUERY_STRING SERVER_PORT 80 CONTENT_LENGTH 72 REQUEST_METHOD POST SCRIPT_NAME /cgi-bin/form.pl HTTP_DNT 1 GATEWAY_INTERFACE CGI/1.1 REQUEST_SCHEME http Uploaded 72 bytes with boundary : eins=Hallo+Robert&mehr=Hallo%0D%0ARobert&datei=Firefox+feeds+backup.opml

          Da fehlt der Dateiinhalt, den ich mit multipart/form-data erhalte.

          Viele Grüße
          Robert

          Folgende Nachrichten verweisen auf diesen Beitrag:

          1. problematische Seite

            hi @Robert B.

            Wenn ich ein klassisches HTML <form> verwende, bekomme ich mit mit deinem binary/name+value nichts hochgeladen:

            <form action="/cgi-bin/form.pl" method="post" enctype="binary/name+value">

            Logisch: Der Browser kennt diesen Enctype nicht. Den musst Du Dir mit JS zusammenbauen und den Request mit AJAX feuern, siehe da

            Was meinen Algorithmus für formdata betrifft, bin ich auf strstr() gekommen. Allerdings krieg ich übr die Pointerarithmetik falsch Längenangaben, ich mach dann mal ein Beispiel (oder was falsch).

            MfG

            PS: Guck Dir mal Transfer-Encoding: chunked an. Da ist der Response Body zerstückelt, aber es sind Längenangaben drin (in HEX), womit man die Chunks wieder exakt zusammensetzen kann. Der Algorithmus ist in der RFC beschrieben und recht einfach nachzubauen. Ein schönes Beispiel für einen Serializealgorithmus.

            Der Schritt zur binären Serialisierung besteht nur darin, die Längenangaben mit dem Datentyp uint32_t zu machen, was sowohl mit pack "V", $len (Perl, PHP) als auch mit JS zu machen ist.

            function gcc(){ var params = sampleform(0); params.unshift({"name":"cc","value":"1"}); var xhr = new XMLHttpRequest(); xhr.open('POST','/c.html'); xhr.setRequestHeader('Content-Type','binary/name+value'); throbber(true); xhr.onload = function(){ throbber(false); var res = JSON.parse(this.response); $("#out").html(xr( $("#tt").html(), {valeins:res.valeins, valzwei:res.valzwei, valdrei:res.valdrei}, true ) ); }; xhr.send( slice2binary(params) ); return false; }

            die Funktionen sind in request.js

            1. Moin pl,

              Wenn ich ein klassisches HTML <form> verwende, bekomme ich mit mit deinem binary/name+value nichts hochgeladen:

              <form action="/cgi-bin/form.pl" method="post" enctype="binary/name+value">

              Logisch: Der Browser kennt diesen Enctype nicht.

              … die HTML-Spezifikation übrigens auch nicht. Kann es sein, dass der enctype korrekterweise binary/x-name+value heißen müsste?

              Der „Vorteil“ deiner Lösung ist jedenfalls, dass ich zusätzlichen Code selbst schreiben muss, während multipart/form-data „out-of-the-box“ funktioniert.

              Den musst Du Dir mit JS zusammenbauen und den Request mit AJAX feuern, siehe da

              Soviel zum Thema Erwartungshaltung: Ich habe anscheinend dein Codebeispiel dort verpasst.

              Was meinen Algorithmus für formdata betrifft, bin ich auf strstr() gekommen. Allerdings krieg ich übr die Pointerarithmetik falsch Längenangaben, ich mach dann mal ein Beispiel (oder was falsch).

              Mit strstr bekommst du Position des ersten gefundenen Zeichens, also im Fall von multipart/form-data wäre das dann das erste - der Boundary, du musst also noch strlen(boundary) dazu addieren (und ein Newline, glaube ich), um an den Anfang des ersten Items zu gelangen.

              PS: Guck Dir mal Transfer-Encoding: chunked an.

              Transfer-Encoding ist auf HTTP-Ebene und nicht auf HTML-Ebene:

              Unlike Content-Encoding (Section 3.1.2.1 of [RFC7231]), Transfer-Encoding is a property of the message, not of the representation,

              enctype ist HTML.

              Da ist der Response Body zerstückelt, aber es sind Längenangaben drin (in HEX), womit man die Chunks wieder exakt zusammensetzen kann. Der Algorithmus ist in der RFC beschrieben und recht einfach nachzubauen. Ein schönes Beispiel für einen Serializealgorithmus.

              … und ein schönes Beispiel, dass „das Internet [immer noch] Neuland ist“: Hyperlinks gibt es seit dem ersten HTML-Entwurf, damit man nicht auf ungerichtet auf aktuell 8521 RFCs verweisen braucht, sondern direkt auf Abschnitt 4.1 des RFC 7230 linken kann.

              Der Schritt zur binären Serialisierung besteht nur darin, die Längenangaben mit dem Datentyp uint32_t zu machen, was sowohl mit pack "V", $len (Perl, PHP) als auch mit JS zu machen ist.

              Das ist in deinem proprietären Beispiel so, ja.

              Viele Grüße
              Robert

              1. Moin ägein,

                Der Schritt zur binären Serialisierung besteht nur darin, die Längenangaben mit dem Datentyp uint32_t zu machen, was sowohl mit pack "V", $len (Perl, PHP) als auch mit JS zu machen ist.

                Das ist in deinem proprietären Beispiel so, ja.

                Und hat sich seit Jahren bewährt 😉

                Nicht nur Plattformübergreifend sondern auch Programming Language übergreifend!

                Bis denne. Memcompare..

          2. Problem mit Boundary:

            // der ganze POST liegt im RAM char *mem; mem = (char*)malloc(content_length); fread(mem,content_length,1,stdin); // Soweit OK // bis zur 1. Leerzeile duchgehangelt offs = bline; // Zeiger umsetzen char *endbin; // Ende Binary int vlen = 0; // Binary, Value length endbin = strstr(offs,bdy);// suche die Boundary vlen = endbin - offs; fprintf(stderr, "vlen %d %p\n", vlen, endbin);

            Bekomme ich die exakte Längenabgabe und einen Pointer auf endbin, solange das eine Textdatei ist (vlen 965 0061D743). Wenn jedoch eine Binärdatei hochgeladen wird, steht der Pointer endbin auf 00000000.

            Whats wrong!?

            MfG

            1. Moin noch einmal,

              Bekomme ich die exakte Längenabgabe und einen Pointer auf endbin, solange das eine Textdatei ist (vlen 965 0061D743). Wenn jedoch eine Binärdatei hochgeladen wird, steht der Pointer endbin auf 00000000.

              Whats wrong!?

              strstr-Doku nicht gelesen:

              The behavior is undefined if either str or substr is not a pointer to a null-terminated byte string.

              Viele Grüße
              Robert

              1. So isses. strstr() ist die falsche Funktion. Da gibts aber noch eine die bytegenau arbeitet, die werd' ich nehmen und dann klappts auch mit der Binary 😉

              2. hi @Robert B.

                strstr-Doku nicht gelesen:

                Dochdoch 😉

                Hier ist die Alternative binary safe und damit funktioniert der Algorithmus. Allerdings hab ich hin und wieder einen Absturz, wenn Du Zeit hast, guck mal drüber, würde mich sehr freuen 😉

                else if(getenv("CONTENT_TYPE") && strncmp(getenv("CONTENT_TYPE"), "multipart/form-data", 19) == 0){ unsigned int content_length = getenv("CONTENT_LENGTH") ? atoi(getenv("CONTENT_LENGTH")) : 0; //if( content_length > MAXLEN ) return warn("Upload MAXLEN!"); unsigned char *mem; mem = (char*)malloc(content_length); fread(mem,content_length,1,stdin); // Boundary in erster Zeile ermitteln unsigned char *offs; // Wanderzeiger Offset int blen = 0; // Boundary Length char *boundary; if( offs = strstr(mem, "\r\n")){ blen = offs - mem; boundary = (char*)malloc(blen); memcpy(boundary,mem,blen); offs = mem; fprintf(stderr,"Boundary %p %d %s\n", mem, blen, boundary); } else{ return warn("Boundary Not Found!"); } // Ab hier wird es zyklisch do{ PARAM *pnew = NULL; pnew = malloc(sizeof *pnew); int hlen = 0; // in Header: name, filename, Content-Type char *header; unsigned char *h; // Hilfszeiger if( h = strstr(offs, "\r\n\r\n") ){ // Bis zur Leerzeile hlen = h - offs; header = (char*)malloc(hlen); memcpy(header,offs,hlen); offs = h; if( h = strstr(header,"Content-Type") ){ pnew->content_type = (char*)malloc(hlen); sprintf(pnew->content_type,"%s",h+14); pnew->content_type[hlen] = 0; } else pnew->content_type = ""; if( h = strstr(header, "filename")) { for(int i = 0; i < hlen; i++){ if(h[i] == 34) h[i] = 0; } pnew->filename= (char*)malloc(hlen); sprintf(pnew->filename,"%s",h+10); } else { pnew->filename = "" ;} h = strstr(header,"name"); for(int i = 0; i < hlen; i++){ if(h[i] == 34) h[i] = 0; } pnew->name= (char*)malloc(hlen); sprintf(pnew->name,"%s",h+6); free(header); fprintf(stderr,"Header %s %s %s \n", pnew->content_type, pnew->filename, pnew->name); } else return warn("Header not Found!"); int vlen = 0; // Value length, Binary unsigned char *b; // Begin Boundary // offs steht am Anfang der Leerzeile !! if( b = bstrstr(offs, content_length, boundary, blen) ){ offs += 4; // 4 bytes vor wegen der Leerzeile vlen = b - offs - 2; // 2 bytes abziehen wg. Zeilenumbruch vor Boundary pnew->value = (char*)malloc(vlen); memcpy(pnew->value,offs,vlen); pnew->value[vlen] = 0; pnew->vlen = vlen; offs = offs + vlen + blen + 6; //fprintf(stderr,"Vlen %d \n", vlen); }else return warn("Binary Not Found!"); pnew->next = NULL; while( *par != NULL ){ par = &(*par)->next; } *par = pnew; done++; fprintf(stderr,"Remains %d \n", content_length -(offs - mem)); }while( content_length -(offs - mem) > 0 ); return done;

                Mit dem Header Parser bin ich auch nicht gerade glücklich. Das geht bestimmt auch einfacher,

                MfG

                1. Moin,

                  überschlafen und verbessert 😉 :Die Parameter nicht kopieren, sondern einfach die Zeiger welche das PARAM Struct definiert, auf den jeweiligen Speicherbereich setzen (im Hauptspeicher liegt der gesamte POST).

                  Sieht gut aus, danke @Robert B.

                  PS: Und die Dateien serverseitig am Stück auf die Platte schreiben, also nicht byteweise.

                  Mehr Komfort beim Upload bietet ein proprietärer Enctype. Verbessern geht immer!

                  1. Moin pl,

                    Die Parameter nicht kopieren, sondern einfach die Zeiger welche das PARAM Struct definiert, auf den jeweiligen Speicherbereich setzen (im Hauptspeicher liegt der gesamte POST).

                    Ach was. 😉

                    Mehr Komfort beim Upload bietet ein proprietärer Enctype. Verbessern geht immer!

                    Dann reiche deinen Vorschlag zur Standardisierung ein.

                    Viele Grüße
                    Robert

                2. Hallo pl,

                  [… Code-Monster …]

                  Die Funktion (ich gehe davon aus, dass das eine Funktion ist) macht viel zu viel. Sie ist zu lang, modularisiere viel mehr. Beispiel:

                  else if(getenv("CONTENT_TYPE") && strncmp(getenv("CONTENT_TYPE"), "multipart/form-data", 19) == 0){

                  Der Parser für multi-part/for-data sollte eine eigene Funktion sein.

                  // Boundary in erster Zeile ermitteln

                  Die Ermittlung der Boundary ist eine eigene Funktion wert.

                  if( h = strstr(offs, "\r\n\r\n") ){ // Bis zur Leerzeile

                  Auch das parsen der Header ist eine eigene Funktion.

                  Und so weiter… you get the idea.

                  Eine einzelne Funktion sollte so kurz wie möglich und so lang wie nötig sein. Als Faustregel: wenn eine Funktion länger als 15 Zeilen ist, dann hast du nicht genug modularisiert.

                  Ein paar Anmerkungen zum Code noch: vermeide doppelte Funktionsaufrufe und speichere die Ergebnisse lieber in einer Variablen zwischen und/oder kapsele es in einer Funktion. Beispiel:

                  else if(getenv("CONTENT_TYPE") && strncmp(getenv("CONTENT_TYPE"), "multipart/form-data", 19))

                  statt dieses Checks im else if mach das lieber in einer Funktion:

                  int is_multipart_formdata() { const char *ctype = getenv("CONTENT_TYPE"); return ctype && strcmp(ctype, "multipart/form-data") == 0; } // … else if(is_multipart_formdata()) {

                  Der Compiler wird so kurze Funktionen mit hoher Wahrscheinlichkeit inlinen, und es ist viel einfacher zu lesen.

                  LG,
                  CK

                  -- https://wwwtech.de/about

                  Folgende Nachrichten verweisen auf diesen Beitrag:

                  1. Es gibt immer noch Abstürze die ich mir nicht erklären kann. Sie haben jedoch nichts mit Modularisierung zu tun.

                    Zum Vergleich: ein Upload 35 Dateien, 75 MB funktioniert sauber mit meinem Enctype binary/name+value der auch mit C deserialisiert wird. Mit multipart/form-data hingegen stürzt bei derselben Datenmenge mein C Programm ab.

                    MfG

                    1. Hallo pl,

                      Es gibt immer noch Abstürze die ich mir nicht erklären kann. Sie haben jedoch nichts mit Modularisierung zu tun.

                      Modularisierung verbessert die Lesbarkeit und isoliert die Umgebung, in der der Code arbeitet. Die Abstürze sind höchstwahrscheinlich auf Probleme mit deinem Speicher-Management zurückzuführen. Um das zu debuggen ist ein sauber strukturierter Code eine deutliche Erleichterung.

                      Oder um es so zu sagen: ich bin nicht bereit, deinen Code so, wie er ist, zu debuggen. Zu schlecht lesbar, zu schlecht aufbereitet.

                      LG,
                      CK

                      -- https://wwwtech.de/about
                      1. hi

                        Es gibt immer noch Abstürze die ich mir nicht erklären kann. Sie haben jedoch nichts mit Modularisierung zu tun.

                        Modularisierung verbessert die Lesbarkeit und isoliert die Umgebung, in der der Code arbeitet. Die Abstürze sind höchstwahrscheinlich auf Probleme mit deinem Speicher-Management zurückzuführen.

                        Nein, der Fehler liegt woanders, der Fehler tritt nämlich auch bei kleineren Datenmengen auf und ist reproduzierbar: bis 13 Dateien tuts, ab 14 Dateien bleibt das Programm eine Weile hängen und stürzt dann ab.

                        Da stimmt was mit der Abbruchbedingung nicht. Im Übrigen sehe ich keinen Grund dafür daß 100 Zeilen Code schwer lesbar und zu debuggen sind, die ganze Schleife passt in mein Editorfenster. Immerhin hab ich den Fehler ja gefunden!

                        MfG

                        1. Hallo pl,

                          Im Übrigen sehe ich keinen Grund dafür daß 100 Zeilen Code schwer lesbar und zu debuggen sind,

                          Wenn das bei Dir auf einen Bildschirm passt, hast Du entweder einen Winz-Font oder einen Riesenscreen :)

                          Ich stimme CK aber zu: Das ist zu viel. Modularisierung mit gut gewählten Funktionsnamen hilft eine Menge. Das ist die halbe Doku.

                          Rolf

                          -- sumpsi - posui - clusi
                          1. weißt Du @Rolf B

                            ich bin zwar noch ein bischen doof in C aber wie ich meinen Code zu strukturieren habe, ist hier kein Thema, dafür hab ich lang genug in Perl entwickelt. Und was die Schleife über die Zyklen betrifft die sich aus dem Enctype multipart/form-data ergeben, da gibt es strukturell nichts zu kritisieren, denselben Algorithmus schreibe ich in Perl genauso hin.

                            Und wie das in C so ist: Abstürze passieren oftmals an einer ganz anderen Stelle als da wo man sie zunächst vermutet, das ist der Unterschied zu Perl. Aus dem leider auch folgt, daß man für ein C Programm halt ein bischen länger braucht.

                            Aber schließlich ist alles erlernbar. Man darf nur nicht loslassen, sondern an seinen Ideen festhalten!

                            Schönen Tag noch.

                            Ach Übrigens:

                            int content_length = getenv("CONTENT_LENGTH") ? atoi(getenv("CONTENT_LENGTH")) : 0;

                            Eine Diskussion darüber ob ein Aufruf der getenv() Funktion genügen würde ist genauso überflüssig wie eine Diskussion über den von atoi() zrückgegeben Datentyp. Jeder Webserver würde negative Angaben in Content-Length sofort als 400 Bad Request abschmettern, das kommt gar nicht erst in der Umgebung an!

                            1. Moin pl,

                              Und wie das in C so ist: Abstürze passieren oftmals an einer ganz anderen Stelle als da wo man sie zunächst vermutet, das ist der Unterschied zu Perl. Aus dem leider auch folgt, daß man für ein C Programm halt ein bischen länger braucht.

                              In hat man die Freiheit sich um vieles selbst zu kümmern – aber auch die Verantwortung dies tun zu müssen. So einfach ist das.

                              Ach Übrigens:

                              int content_length = getenv("CONTENT_LENGTH") ? atoi(getenv("CONTENT_LENGTH")) : 0;

                              Eine Diskussion darüber ob ein Aufruf der getenv() Funktion genügen würde ist genauso überflüssig wie eine Diskussion über den von atoi() zrückgegeben Datentyp.

                              Es ist die Performance deines Frameworks, nicht unseres 😉

                              Jeder Webserver würde negative Angaben in Content-Length sofort als 400 Bad Request abschmettern, das kommt gar nicht erst in der Umgebung an!

                              Das schließt nicht aus, dass es nicht trotzdem auch passieren kann. Die Prüfung darauf ist deutlich billiger als ein negativer Wert, der im Aufruf von malloc zu einem sehr großen positiven wird.

                              Viele Grüße
                              Robert

                        2. Hallo pl,

                          Modularisierung verbessert die Lesbarkeit und isoliert die Umgebung, in der der Code arbeitet. Die Abstürze sind höchstwahrscheinlich auf Probleme mit deinem Speicher-Management zurückzuführen.

                          Nein, der Fehler liegt woanders, der Fehler tritt nämlich auch bei kleineren Datenmengen auf

                          Speichermanagement heisst nicht „du reservierst zu viel Speicher.“ Speicher-Management bezeichnet die Art und Weise, wie du mit deinem Speicher umgehst. Du wirst irgendwo einen String nicht null-terminiert haben und einen Out-Of-Bounds-Read oder -Write haben oder sowas. Das übliche eben, wenn man C schreibt.

                          LG,
                          CK

                          -- https://wwwtech.de/about
                3. Hallo pl,

                  mem = (char*)malloc(content_length); fread(mem,content_length,1,stdin); if( offs = strstr(mem, "\r\n")){

                  Mein C ist ziemlich eingerostet und wird heutzutage einen Halbton höher gespielt (C#). Aber ich denke, dass dies ein Bug ist, der darauf wartet zu passieren. Du allocierst den Buffer und füllst ihn mittels fread. Ich würde sagen: dadurch wird er nicht Nullterminiert.

                  Und das heißt: wenn kein \r\n im Buffer ist, läufst Du in den Wald.

                  mem = (char*)malloc(content_length+1); fread(mem,content_length,1,stdin); mem[content_length] = 0; if( offs = strstr(mem, "\r\n")){

                  Update: Beim malloc für den header gilt das Gleiche. Nach meiner Meinung brauchst Du da aber nicht unbedingt einen malloc, weil du den kopierten Bereich nicht veränderst. Du KÖNNTEST ihn aber verändern, indem Du zunächst mal alle \n durch \0 ersetzt.

                  Egal ob du das tust oder nicht, würde ich Dir eine Funktion empfehlen, die eine Headerzeile parsed. Du übergibst ihr den Pointer auf den Beginn des Headerbereichs sowie einen Zeiger auf eine Struktur, die einen Header-Descriptor bildet. Das sind im einfachsten Fall zwei char* - dann muss die Funktion aber den Doppelpunkt und das \r\n am Ende der Headerzeile durch \0 ersetzen. Oder Du fügst die Längen als weitere Attribute in die Struktur ein. Die Rückgabe dieser Funktion wäre ein Pointer auf den Beginn der nächsten Headerzeile oder NULL, wenn kein Header mehr kommt (d.h. wenn Du sie mit einem Pointer auf \r\n aufrufst).

                  struct headerInfo { int nameLen; char* name; int valueLen; char* value; }

                  Auf diese Weise hast Du schon mal die technische Interpretation der Headerstruktur und die fachliche Verarbeitung der Headerwerte getrennt.

                  Rolf

                  -- sumpsi - posui - clusi

                  Folgende Nachrichten verweisen auf diesen Beitrag:

                  1. Hallo Rolf,

                    Du allocierst den Buffer und füllst ihn mittels fread. Ich würde sagen: dadurch wird er nicht Nullterminiert.

                    Und das heißt: wenn kein \r\n im Buffer ist, läufst Du in den Wald.

                    Good catch, du hast recht.

                    Genau wegen solcher Verstrickungen und solcher Probleme bin ich (nach 10 Jahren C-Erfahrung) inzwischen der Meinung, dass wir kein C mehr schreiben sollten. Die Sicherheitslücken der letzten 20 Jahre sind zu einem riesigen Teil genau auf diese Problemklasse zurückzuführen. Auch wenn es unbedingt low-level sein muss: es gibt bessere Alternativen.

                    LG,
                    CK

                    -- https://wwwtech.de/about
                  2. Moin Rolf,

                    mein +1 gab es für den Hinweis auf den Fehler nicht auf diese Behauptung:

                    Mein C ist ziemlich eingerostet und wird heutzutage einen Halbton höher gespielt (C#)

                    😉

                    Viele Grüße
                    Robert

                  3. hi @Rolf B

                    mem = (char*)malloc(content_length); fread(mem,content_length,1,stdin); if( offs = strstr(mem, "\r\n")){

                    Mein C ist ziemlich eingerostet und wird heutzutage einen Halbton höher gespielt (C#). Aber ich denke, dass dies ein Bug ist, der darauf wartet zu passieren. Du allocierst den Buffer und füllst ihn mittels fread. Ich würde sagen: dadurch wird er nicht Nullterminiert.

                    Wir haben hier eine Binary!

                    Und das heißt: wenn kein \r\n im Buffer ist, läufst Du in den Wald.

                    In einer Binary gibt es Bytes mit beliebigen Wertigkeiten. Außer daß sich C hier etwas eigentümlich verhält, hat das nichts mit C zu tun.

                    Und setmode() war das nicht Dein Vorschlag!? Genau das ist ja entscheidend.

                    MfG

                    1. Hallo pl,

                      mem = (char*)malloc(content_length); fread(mem,content_length,1,stdin); if( offs = strstr(mem, "\r\n")){

                      Mein C ist ziemlich eingerostet und wird heutzutage einen Halbton höher gespielt (C#). Aber ich denke, dass dies ein Bug ist, der darauf wartet zu passieren. Du allocierst den Buffer und füllst ihn mittels fread. Ich würde sagen: dadurch wird er nicht Nullterminiert.

                      Wir haben hier eine Binary!

                      Und wir haben oben den Aufruf von strstr.

                      Viele Grüße
                      Robert

                      1. strstr() ist deutlich schneller als bstrstr() und solange es sich um nullterminierte Strings handelt wie die boundary in der 1. Zeile ist strstr() völlig korrekt eingesetzt.

                        MfG

                        1. Moin pl,

                          strstr() ist deutlich schneller als bstrstr() und solange es sich um nullterminierte Strings handelt wie die boundary in der 1. Zeile ist strstr() völlig korrekt eingesetzt.

                          wie es allerdings scheint, ist dein String eben nicht NULL-terminiert. Was passiert denn, wenn du content_length + 1 Byte Speicher holst und das letzte Byte auf '\0' setzt?

                          Viele Grüße
                          Robert

                        2. Hallo pl,

                          strstr() ist deutlich schneller als bstrstr() und solange es sich um nullterminierte Strings handelt wie die boundary in der 1. Zeile ist strstr() völlig korrekt eingesetzt.

                          unsigned char *mem; mem = (char*)malloc(content_length); fread(mem,content_length,1,stdin);

                          Du liest content_length bytes aus stdin in den Buffer mem. Dieser Buffer ist nicht mit einem 0-Byte terminiert.

                          unsigned char *offs; // Wanderzeiger Offset int blen = 0; // Boundary Length char *boundary; if( offs = strstr(mem, "\r\n")){

                          Du rufst strtr() mit mem als Such-Buffer auf. strstr() sucht nach der gewünschten Zeichenfolge im Suchbuffer bis die Zeichenfolge gefunden wurde oder bis das terminierende 0-Byte gefunden wurde.

                          Du hast kein terminierendes 0-Byte, sondern höchstens zufällig eins im Payload. Wenn also kein "\r\n" gefunden wird, hast du einen out-of-bounds read.

                          Das gleiche gilt für spätere strstr()-Calls mit offs als Suchbuffer:

                          if( h = strstr(offs, "\r\n\r\n") ){

                          Außerdem hast du vermutlich noch einen zweiten Bug:

                          bstrstr(offs, content_length, boundary, blen)

                          Ich kenne die Implementation von bstrstr() nicht, aber das zweite Argument wird vermutlich die Länge des Suchbuffers angeben. Du gibst hier content_length an, das ist aber falsch: offs zeigt nicht auf das erste Byte des Buffers, sondern auf ein Byte irgendwo mitten drin. Du hast also nicht mehr content_length als Länge, sondern content_length - (offs - mem).

                          Außerdem ist deine Befüllung von pnew buggy.

                          hlen = h - offs; header = (char*)malloc(hlen); memcpy(header,offs,hlen); offs = h;

                          Du kopierst den Header in einen Buffer, mit memcpy(). Der Header-String ist also nicht 0-terminiert.

                          if( h = strstr(header,"Content-Type") ){

                          Du lässt strstr() auf einen nicht-0-terminierten Buffer los. Wenn also kein "Content-Type" gefunden werden konnte, hast du einen out-of-bounds read.

                          pnew->content_type = (char*)malloc(hlen); sprintf(pnew->content_type,"%s",h+14);

                          Du allozierst hlen Bytes für pnew->content_type. Du terminierst aber an Byte hlen+1:

                          pnew->content_type[hlen] = 0;

                          Das ist ein out-of-bounds write, du musst hlen+1 Bytes allozieren. Aber eigentlich kannst du das manuelle terminieren auch weglassen, denn das macht dein sprintf()-Call bereits. pnew->filename und pnew->name terminierst du ja auch nicht manuell.

                          h = strstr(header,"name"); for(int i = 0; i < hlen; i++){ if(h[i] == 34) h[i] = 0; }

                          Rückgabewert prüfen! Was, wenn strstr() kein name im Suchbuffer finden kann? Dann arbeitest du mit NULL.

                          pnew->value = (char*)malloc(vlen); memcpy(pnew->value,offs,vlen); pnew->value[vlen] = 0;

                          Hier das gleiche. Du allozierst vlen Bytes, schreibst aber an Byte vlen+1.

                          pnew->next = NULL; while( *par != NULL ){ par = &(*par)->next; } *par = pnew;

                          Das kann ich nicht überprüfen, weil mir die Definitionen fehlen und die Info, was par ist. Aber es lässt meine Spinnensinne klingeln…

                          LG,
                          CK

                          -- https://wwwtech.de/about

                          Folgende Nachrichten verweisen auf diesen Beitrag:

                          1. Hallo Christian,

                            pnew->value = (char*)malloc(vlen); memcpy(pnew->value,offs,vlen); pnew->value[vlen] = 0;

                            Hier das gleiche. Du allozierst vlen Bytes, schreibst aber an Byte vlen+1.

                            so einen typischen Anfängerfehler hat wohl jeder C-Programmierer mal gemacht. Bei mir hat sich dieser Fehler erst bemerkbar gemacht, als ich nach über einem Jahr an einer völlig anderen Stelle das Programm erweitert habe. Die Fehlersuche war grausam.

                            Gruß
                            Jürgen

                            1. Hallo JürgenB,

                              so einen typischen Anfängerfehler hat wohl jeder C-Programmierer mal gemacht.

                              Ja. Ich auch. Der ist halt auch besonders gemein, weil bei einem Byte einem die Software oft noch nicht um die Ohren fliegt, sondern erst irgendwann mal, wenn die Speicher-Konstellation ungünstig ist.

                              Bei mir hat sich dieser Fehler erst bemerkbar gemacht, als ich nach über einem Jahr an einer völlig anderen Stelle das Programm erweitert habe. Die Fehlersuche war grausam.

                              Ich habe irgendwann Valgrind entdeckt, das ist wirklich eine enorme Erleichterung gewesen. Solche Speicherbugs sind ansonsten enorm schwierig zu finden, ja.

                              LG,
                              CK

                              -- https://wwwtech.de/about
                    2. Hallo pl,

                      Wir haben hier eine Binary!

                      Uh oh. Dieser Einwand ist doch jetzt hoffentlich nicht dein Ernst? Wenn Du so unterwegs bist, wünsche ich Dir viel Glück auf der Jagd nach dem goldenen BSOD1.

                      Du liest binär, ja, aber du arbeitest nicht mit binary-Funktionen, sondern mit C String Funktionen. Die wissen nichts von der Länge des allocierten Buffers. Sie erwarten eine Nullterminierung des zu bearbeitenden Bereichs.

                      Guck mal ob deine CRT eine strnstr Funktion hat. Der kannst Du die Buffergröße mitgeben und brauchst keine manuelle Nullterminierung (die hatte ich eben nur nicht in der MS DOC gefunden, blöder Verein!)

                      Rolf

                      1. Nein, das heißt nicht Bullshit Of the Day

                      -- sumpsi - posui - clusi
                      1. Hallo Rolf,

                        [Nein, das heißt nicht Bullshit Of the Day

                        Ne, das heisst natürlich „black screen of death“ 😉

                        LG,
                        CK

                        -- https://wwwtech.de/about
                4. Moin pl,

                  else if(getenv("CONTENT_TYPE") && strncmp(getenv("CONTENT_TYPE"), "multipart/form-data", 19) == 0){ unsigned int content_length = getenv("CONTENT_LENGTH") ? atoi(getenv("CONTENT_LENGTH")) : 0;
                  unsigned char *mem; mem = (char*)malloc(content_length); fread(mem,content_length,1,stdin);
                  • Nur der Neugierde halber: Meckert hier nicht der Compiler, weil der Rückgabewert von malloc nur nach char* statt unsigned char* gecastet wird?
                  • Und malloc erwartet natürlich die Speichergröße als size_t – ebenso wie fread.
                  // Boundary in erster Zeile ermitteln unsigned char *offs; // Wanderzeiger Offset int blen = 0; // Boundary Length char *boundary; if( offs = strstr(mem, "\r\n")){ blen = offs - mem;

                  Die Differenz zweier Pointer ist nicht vom Typ int, sondern ptrdiff_t

                  boundary = (char*)malloc(blen); memcpy(boundary,mem,blen);

                  malloc und memcpy erwarten die Längenangabe immer noch als size_t.

                  /* <snip> */ // Ab hier wird es zyklisch do{ PARAM *pnew = NULL; pnew = malloc(sizeof *pnew); int hlen = 0; // in Header: name, filename, Content-Type char *header; unsigned char *h; // Hilfszeiger if( h = strstr(offs, "\r\n\r\n") ){ // Bis zur Leerzeile hlen = h - offs; header = (char*)malloc(hlen); memcpy(header,offs,hlen);

                  Siehe oben.

                  offs = h; if( h = strstr(header,"Content-Type") ){

                  Sind nicht noch andere Schreibweisen des Content-Type erlaubt? Wie case-sensitive ist denn die Definition hier?

                  pnew->content_type = (char*)malloc(hlen); sprintf(pnew->content_type,"%s",h+14);

                  Jedes Mal, wenn ich ein sprintf sehe, ist ja der erste Reflex auf snprintf hinzuweisen, aber wenn ich dann sehe, dass hier lediglich ein String umkopiert wird, warum dann nicht gleich strncpy?

                  pnew->content_type[hlen] = 0; } else pnew->content_type = ""; if( h = strstr(header, "filename")) { for(int i = 0; i < hlen; i++){ if(h[i] == 34) h[i] = 0;

                  Direkt das entsprechende Zeichen zwecks Lesbarkeit hinzuschreiben, war wohl zu viel 😉

                  } pnew->filename= (char*)malloc(hlen); sprintf(pnew->filename,"%s",h+10);

                  Siehe oben – bis zum Ende dieses if-else-Konstrukts.

                  int vlen = 0; // Value length, Binary

                  Das sollte natürlich auch ein ptrdiff_t sein.

                  unsigned char *b; // Begin Boundary // offs steht am Anfang der Leerzeile !! if( b = bstrstr(offs, content_length, boundary, blen) ){ offs += 4; // 4 bytes vor wegen der Leerzeile

                  Das wird nicht geprüft, sondern nur vorausgesetzt!

                  /* Siehe oben */

                  Viele Grüße
                  Robert

  2. Fürs Arschiv: setvbuf(out, NULL, _IOFBF, 1024); löste mein Problem mit den spradischen Abstürzen.

    In C muss man sich eben um alles selber kümmern 😉

    Generalprobe, Enctype multipart/form-data, 476 Dateien, 180 MB läuft nun durch. Das Problem lag also beim Schreiben der Uploads auf die Platte.

    MfG

    1. Generalprobe, Enctype multipart/form-data, 476 Dateien, 180 MB läuft nun durch.

      Oh. Du schreibst was in C um DDoS-Attacken einfacher zu machen? Da wird Dein "Framework" wohl noch ein paar Zeilen zum Thema "Sicherheit" nötig haben.

      1. Generalprobe, Enctype multipart/form-data, 476 Dateien, 180 MB läuft nun durch.

        Oh. Du schreibst was in C um DDoS-Attacken einfacher zu machen?

        Und Du schreibst Unsinn!

        1. Hallo pl,

          Generalprobe, Enctype multipart/form-data, 476 Dateien, 180 MB läuft nun durch.

          Oh. Du schreibst was in C um DDoS-Attacken einfacher zu machen?

          Und Du schreibst Unsinn!

          Nein, er hat recht.

          Dein Algorithmus ist ausgezeichnet dazu geeignet, um DoS-Attacken zu fahren. Du reservierst da einmal den vollständigen Speicher für den request body. Ich muss also nur herausfinden, was deine maximale upload size ist und dich dann mit entsprechend maximal großen Requests vollbomben. Du erlaubst offensichtlich gerade 180MB, also erzeuge ich 1000 Requests mit 180MB request body und schon habe ich dich dazu gebracht, 1000*180 MB = 180000 MB = ungefähr 175GB Speicher zu reservieren. Das legt jede Kiste lahm.

          LG,
          CK

          -- https://wwwtech.de/about
          1. hi CK,

            Generalprobe, Enctype multipart/form-data, 476 Dateien, 180 MB läuft nun durch.

            Oh. Du schreibst was in C um DDoS-Attacken einfacher zu machen?

            Und Du schreibst Unsinn!

            Nein, er hat recht.

            Nein hat er nicht! Denn Entwickeln+Testen zielen ja darauf ab, Atacken zu erkennen und abzuwehren!

            Dein Algorithmus ist ausgezeichnet dazu geeignet, um DoS-Attacken zu fahren.

            Nein, ist er nicht. Eine diesbezügliche Abwehr erfolgt nämlich vorher, bevor der Algorithmus überhaupt zur Anwendung kommt!

            Du reservierst da einmal den vollständigen Speicher für den request body.

            Das macht der PHP Parser, der in C implementiert ist, auch.

            Ich muss also nur herausfinden, was deine maximale upload size ist

            Das kannst Du gar nicht herausfinden! Das ist nämlich intern konfiguriert, da müsstest Du schon die Konfiguration kennen. Und selbst wenn Du diese Größe kennst, bleibt eine Überschreitung derer ohne Effekt.

            MfG

            PS: Beschreib doch mal Deinen Algorithmus!

            1. Hallo pl,

              Generalprobe, Enctype multipart/form-data, 476 Dateien, 180 MB läuft nun durch.

              Oh. Du schreibst was in C um DDoS-Attacken einfacher zu machen?

              Und Du schreibst Unsinn!

              Nein, er hat recht.

              Nein hat er nicht! Denn Entwickeln+Testen zielen ja darauf ab, Atacken zu erkennen und abzuwehren!

              Und deshalb ist die Kritik unberechtigt? 🤨 Machst du keine Fehler oder warum ist das ein valides Argument?

              Dein Algorithmus ist ausgezeichnet dazu geeignet, um DoS-Attacken zu fahren.

              Nein, ist er nicht. Eine diesbezügliche Abwehr erfolgt nämlich vorher, bevor der Algorithmus überhaupt zur Anwendung kommt!

              Wie willst du das denn abwehren? DoS können selbst die grössten Tech-Unternehmen der Welt nicht effektiv abwehren, wie willst du das dann hinbekommen? 😉

              Du reservierst da einmal den vollständigen Speicher für den request body.

              Das macht der PHP Parser, der in C implementiert ist, auch.

              Nein, das stimmt nicht. Sie lesen sowohl den Request Body in Chunks ein als auch den Multipart Body. Siehe die Funktionen fill_buffer und rfc1867_post_handler in der Datei main/rfc1867.c im PHP-Sourcecode. Der Request-Body wird Chunk für Chunk ausgewertet (PHP hat dafür eine Datenstruktur multipart_buffer) und der Multipart Body selber wird dann Chunk für Chunk in eine temporäre Datei geschrieben.

              Die einzige Ausnahme ist, wenn es sich bei dem Multipart-Teil nicht um eine Datei handelt. Dann wird der Multipart-Body in einen einzigen zusammenhängenden Speicherblock gelesen.

              Ich muss also nur herausfinden, was deine maximale upload size ist

              Das kannst Du gar nicht herausfinden!

              Warum nicht? Ich schicke dir einfach 1GB und schaue, ob es durchgeht. Wenn nicht, schicke ich dir 512MB und schaue, ob es durchgeht. Wenn ja, schicke ich dir 768 MB und schaue ob es durchgeht. So bekomme ich mit Mittel mit O(log n) Versuchen heraus, wie deine Request-Grösse ist.

              Aber ich muss es gar nicht so genau wissen. Mir reicht ja schon die Info, wie gross ich meinen Payload machen darf. Da kann ich sogar einfach stumpf bei 1GB anfangen und jeweils 100MB abziehen, um herauszufinden, wieviel ich dir schicken kann.

              LG,
              CK

              -- https://wwwtech.de/about
              1. hi CK,

                Aber ich muss es gar nicht so genau wissen.

                Ich kanns Dir sogar sagen: Für diese Seite betragt die maximale Uploadgröße genau 1 Byte. Das kannst Du gerne testen, das Ergebnis steht in der Response 😉

                .

              2. Übrigens:

                Array ( [file] => Array ( [name] => _einfachnurtext.txt [type] => text/plainEinfach nur text.-----------------------------243061247418173 [tmp_name] => C:\WINDOWS\Temp\php16.tmp [error] => 0 [size] => 1 ) )

                Fehler provoziert durch Weglassen der Leerzeile nach dem Payload. Somit erscheint die Boundary im Ergebnis. Ein RFC gerechter Parser darf sowas nicht durchgehen lassen!

                MfG

                PS: Mein Parser toleriert das nicht, sondern bricht das gesamte Parsing ab. Mit Fehlermeldung im Serverlog.

                1. Hallo,

                  dass PHP 5.3 nicht mehr Stand der Technik ist, weißt du?
                  Ansonsten wäre es schön, die gesamte Anfrage inkl. PHP-Script zu sehen.

                  Gruß
                  Patrick

                  1. Hallo,

                    Ansonsten wäre es schön, die gesamte Anfrage inkl. PHP-Script zu sehen.

                    PHP: print_r($_FILES):

                    Und die Leerzeilen in multipart/form-data bechreibt der RFC. Es ist ein Unding daß ein Parser fehlende Leerzeilen durchgehen läßt, da verschieben sich sämtlich Inhalte im Ergebnis!

                    MfG

                    1. Hallo,

                      Ansonsten wäre es schön, die gesamte Anfrage inkl. PHP-Script zu sehen.

                      PHP: print_r($_FILES):

                      Und die Leerzeilen in multipart/form-data bechreibt der RFC. Es ist ein Unding daß ein Parser fehlende Leerzeilen durchgehen läßt, da verschieben sich sämtlich Inhalte im Ergebnis!

                      Ich würde aber schon gerne die komplette Request von dir sehen (gerne anonymisiert), dann kann man es besser nachstellen.

                      Gruß
                      Patrick

                      1. Der Typ sieht so aus (ein input type=file und der button):

                        -----------------------------124754482152 Content-Disposition: form-data; name="file"; filename="_einfachnurtext.txt" Content-Type: text/plain Einfach nur text. -----------------------------124754482152 Content-Disposition: form-data; name="upload" 1 -----------------------------124754482152--

                        und beachte dass in der Deklaration im Header

                        Content-Type: multipart/form-data; boundary=---------------------------124754482152

                        die Boundary am Anfang um 2 Bindestriche kürzer ist. Idealerweise sind die Zeilenumbrüche CRLF, zum Senden eignet sich also eine <textarea> ganz gut. Die Boundary am Ende bekommt 2 Bindestriche und einen CRLF angehängt. Steht alles im RFC. Teste Deine Server, fehlende Leerzeilen bewirken Pufferüberläufe und das ist ein Sicherheitsproblem.

                        MfG

                        1. Hallo pl,

                          wenn ich dich richtig verstanden habe, muss ich den von dir genannten Request Header und Request Body an ein PHP-Script senden. Das habe ich dann mit Firefox HTTP request maker gemacht.

                          Der beschriebene Effekt tritt unter PHP 7.2.11 und 7.2.14 nicht auf, ich erhalte folgende Struktur, wenn ich print_r($_FILES) aufrufe:

                          Array( [file] => Array ( [name] => _einfachnurtext.txt [type] => text/plain [tmp_name] => C:\Windows\php7DD6.tmp [error] => 0 [size] => 18 ) )

                          Oder habe ich da etwas falsch verstanden?

                          Gruß
                          Patrick

            2. Hallo pl,

              PS: Beschreib doch mal Deinen Algorithmus!

              das hast du nachträglich drangehängt, du Schlingel 😉

              Wenn es dich wirklich interessiert, dann kann ich gerne beschreiben, wie ich sowas implementieren würde. Wenn es dir aber nur ums recht haben geht, dann habe ich da keine Lust drauf.

              Wie sieht es also aus?

              LG,
              CK

              -- https://wwwtech.de/about
            3. hi CK,

              Generalprobe, Enctype multipart/form-data, 476 Dateien, 180 MB läuft nun durch.

              Oh. Du schreibst was in C um DDoS-Attacken einfacher zu machen?

              Und Du schreibst Unsinn!

              Nein, er hat recht.

              Nein hat er nicht!

              Hab ich doch. Gemäß Deiner eigenen Beschreibung lief gerade ein Upload in folgenden Dimensionen durch:

              • 476 Dateien,
              • insgesamt 180 MB

              Das ist nahe dran an DDoS und erlaubt die Frage, ob Du das Zeug schreibst um DDoS-Attacken einfacher zu machen. Dahinter versteckt sich der Sinn, dass durch die Blume vorgeschlagen wird, an eine (konfigurierbare) Möglichkeit der Begrenzung der Uploads zu denken.

              Und wenn man an eine solche gedacht hat, dann kann man einfach mit etwas wie "Nönö. Das ist nur ein Test. Im Realbetrieb setze ich da natürlich sinnvolle Grenzen." antworten statt was von "Unsinn" zu schreiben.

    2. Hallo pl,

      ich frage mich, warum ich mir die Mühe gemacht habe, dir Hilfestellung zu geben. Du ignorierst sie ja doch nur. Wie ich bereits letzthin schrieb: dein Verhalten lässt nicht erkennen, dass du an einem Diskurs oder daran zu lernen interessiert bist. Ich werde diese Zeit nicht nochmal investieren.

      Fürs Arschiv: setvbuf(out, NULL, _IOFBF, 1024); löste mein Problem mit den spradischen Abstürzen.

      Das ist nur Zufall. Deine Probleme liegen woanders. Und es sind schwerwiegende Probleme; mit dieser Art von Bugs werden Kisten aufgemacht!

      Ich möchte nochmal in aller Deutlichkeit darauf hinweisen, dass du diesen Code nicht im Produktivbetrieb einsetzen solltest! Er hat schwerwiegende Bugs, die die Sicherheit der Umgebung stark beeinträchtigen!

      LG,
      CK

      -- https://wwwtech.de/about
      1. Hallo Christian Kruse,
        hallo pl,

        Fürs Arschiv: setvbuf(out, NULL, _IOFBF, 1024); löste mein Problem mit den spradischen Abstürzen.

        Das ist nur Zufall. Deine Probleme liegen woanders. Und es sind schwerwiegende Probleme; mit dieser Art von Bugs werden Kisten aufgemacht!

        Ich möchte nochmal in aller Deutlichkeit darauf hinweisen, dass du diesen Code nicht im Produktivbetrieb einsetzen solltest! Er hat schwerwiegende Bugs, die die Sicherheit der Umgebung stark beeinträchtigen!

        Deshalb setze ich den Thread (vorerst) auf no-archive. Vorwürfe deinerseits wegen Arroganz, mangelnder Erfahrung und Nichtbeachtung der Energie des Verstehens nehme ich dafür billigend in Kauf.

        Bis demnächst
        Matthias

        -- Pantoffeltierchen haben keine Hobbys.