Henryk Plötz: gzip decodieren

Beitrag lesen

Moin,

Das geht. Ich hatte das mal für den Server hier gemacht und dabei erfogreich das mit den chunks ignoriert - es scheint nicht immer benutzt zu werden.

Die Länge des ersten Chunks wird auf jeden Fall gesendet, also muß man alles bis zum ersten Zeilenumbruch immer wegschneiden. Ob danach noch weitere Chunks kommen, kommt drauf an. Wenn zuerst die gesamte Ausgabe gepuffert wird - zum Beispiel um sie noch durch ein gzcompress() zu jagen - , ist es sehr wahrscheinlich, dass dann nur noch der Endchunk kommt. Wenn aber zwischenzeitlich mal ein flush() gemacht wird oder die Ausgabe ins Stocken gerät und sich der Server entscheidet das was er jetzt hat schon zu senden, bekommt man damit Probleme.

Henryk hatte dazu mal ein komplettes Script gepostet:
http://forum.de.selfhtml.org/archiv/2002/7/18543/#m108064
Der Aufwand ist wie üblich die Fehlerbehandlung (und das un-chunken)

Vorsicht! Das dekomprimiert die Ausgabe des SBU-Interfaces. In dem Code da ist nichts enthalten was eine gzip-Ausgabe wie sie auch von Content-Encoding: gzip kommt, korrekt verarbeiten könnte.

Aber wie der Zufall es will, habe ich vor nicht allzu langer Zeit ein bisschen gebastelt und dabei auch Dekodierer für chunked und gzip gebaut.

Einmal für chunked (Eingabe in $body, Ausgabe wieder dorthin):

$newbody = "";
     if(preg_match("!^\s*([0-9a-f]+).*?\015?\012(.*)$!is", $body, $matches)) {
      $body = $matches[2];
      $len = base_convert(strtolower($matches[1]), 16, 10);
      while($len > 0) {
       $newbody .= substr($body, 0, $len);
       $body = substr($body, $len+2); // CRLF auch noch wegwerfen
       if(preg_match("!^\s*([0-9a-f]+).*?\015?\012(.*)$!is", $body, $matches)) {
        $len = base_convert(strtolower($matches[1]), 16, 10);
        $body = $matches[2];
       } else die("Protocol violation in line ".__LINE__);
      }
      $body = $newbody; unset($newbody);
     } else die("Protocol violation in line ".__LINE__);

Da bin (naja, zumindest war es als ich den Code das letzte mal angesehen habe) ich mir ziemlich sicher, dass es seine Sache korrekt macht, wenn der Server seine Sache korrekt macht.

Die Erklärung ist auch recht einfach: Zunächst mal nachschauen, ob wir da auch wirklich ein paar Hexadezimalziffern auf einer Zeile gekriegt haben, und wenn ja, diese vom Rest trennen. Dann diese Zahl in eine richtige Zahl konvertieren und soviele Bytes aus dem alten in den neuen Body packen. Danach das Spiel solange wiederholen, bis die Länge nicht mehr über 0 liegt.
Es sollte trivial sein den Code so umzuschustern, dass er nicht von einem String in einen anderen arbeitet, sondern wirklich von einem Stream nur die nötigen Bytes in einen String liest.

Dann noch gzip und deflate. Da muß ich zugeben, dass ich reichlich verwirrt bin und der Code wahrscheinlich nur so tut, als würde er funktionieren. Das hat er aber bisher (zumindest zusammen mit mod_gzip auf der Serverseite) recht zuverlässig getan:

(zusätzliche Eingabe noch $encoding mit dem Token welches als Content-Encoding in der Antwort angegeben wurde)

do {
      if(bin2hex(substr($body,0,2)) != "1f8b") break;
      $bodylen = strlen($body);
      $compressmethod = $body[2];
      $flags = ord($body[3]);
      $start = 10;
      if($flags & 4) $start += $body[$start] + $body[$start+1]*256; // extra field überspringen
      if($flags & 8) { while($start < $bodylen && ord($body[$start])!=0) $start++; // Originaldateinamen
                       $start++; }
      if($flags & 16) { while($start < $bodylen && ord($body[$start])!=0) $start++; // Kommentar
                       $start++; }
      if($flags & 32) $start += 2;
      if($start >= $bodylen) break;
      if($compressmethod == "\x8") {
       if(strtolower($encoding) == "gzip") {
        if($newbody = gzinflate(substr($body, $start, -8))) {
         $body = $newbody; unset($newbody);
        }
       } else {
        if($newbody = gzuncompress(substr($body, $start))) {
         $body = $newbody; unset($newbody);
        }
       }
      }
     } while(false);

Zusätzlich zu den Kommentaren noch als Erklärung: 1fb8 ist das Erkennungszeichen für gzip-Dateien und immer fest. Direkt danach steht ein Byte welches die Kompressionsmethode angibt (8h bedeutet deflate). Hier bin ich halt kräftig verwirrt, weil Content-Encoding: gzip üblicherweise mit Kompressionsmethode deflate verschickt wird, es aber noch zusätzlich Content-Encoding: deflate gibt. Wäre nett, wenn das noch jemand aufklären könnte.
Naja, und dann schmeisst der Scriptfetzen halt noch ein paar Daten (die in dem erwähnten RFC beschrieben sind) aus dem Dateiheader weg und übergibt den Rest an gzinflate() oder gzuncompress().

--
Henryk Plötz
Grüße aus Berlin
~~~~~~~~ Un-CDs, nein danke! http://www.heise.de/ct/cd-register/ ~~~~~~~~
~~ Help Microsoft fight software piracy: Give Linux to a friend today! ~~