gzip decodieren
Neelhiks
- php
0 Christian Seiler0 Neelhiks0 Christian Seiler0 Neelhiks0 Sönke Tesch0 Neelhiks
0 Sven Rautenberg0 Carsten
Hi!
Ich suche eine Funktion zum decodieren eines gzip-codierten String. Bisher hab ich nur eine zum codieren gefunden. Geht doch aber bestimmt auch andersherum.
Bye
Neelhiks
Hallo Neelhiks,
Ich suche eine Funktion zum decodieren eines gzip-codierten String. Bisher hab ich nur eine zum codieren gefunden. Geht doch aber bestimmt auch andersherum.
http://de3.php.net/manual/de/function.gzuncompress.php
http://de3.php.net/manual/de/function.gzdeflate.php
Viele Grüße,
Christian
Diese beiden Funktionen sind aber die Gegenstücke zu gzcompress und gzinflate. Muss ich sie irgendwie kombinieren, um den gzip-codierten String zu dekodieren?
Hallo Neelhiks,
Diese beiden Funktionen sind aber die Gegenstücke zu gzcompress und gzinflate. Muss ich sie irgendwie kombinieren, um den gzip-codierten String zu dekodieren?
Nein. Eine von beiden wird funktionieren. Probiers einfach mal aus.
Viele Grüße,
Christian
Funktioniert nicht. Also ich habe mit fsockopen, fputs und dann fgets folgendes zurückbekommen:
HTTP/1.1 200 OK Date: Sun, 18 May 2003 13:00:17 GMT Server: Apache/1.3.26 (Unix) mod_gzip/1.3.19.1a PHP/4.2.2 mod_gzip/1.3.19.1a mod_fastcgi/2.2.12 mod_perl/1.27 mod_ssl/2.8.10 OpenSSL/0.9.6b X-Powered-By: PHP/4.2.2 X-Accelerated-By: PHPA/1.3.3r1 Expires: Thu, 19 Nov 1981 08:52:00 GMT Last-Modified: Sun, 18 May 2003 13:00:17 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Content-Encoding: gzip Connection: close Transfer-Encoding: chunked Content-Type: text/html
danach kam Durcheinander, was mir wie komprimiert aussieht. Wie genau muss ich nun vorgehen, um das zu dekomprimieren? Ich habe dann erstmal das "Durcheinander" in einen String gepackt. Jedoch lies sich der mit keiner der Funktionen decodieren.
Funktioniert nicht. Also ich habe mit fsockopen, fputs und dann fgets folgendes zurückbekommen:
HTTP/1.1 200 OK
Content-Encoding: gzip
Transfer-Encoding: chunked
danach kam Durcheinander, was mir wie komprimiert aussieht. Wie genau muss ich nun vorgehen, um das zu dekomprimieren?
Die Kodierung gzip verwendet das Format des gleichnamigen, in der Unix-Welt weit verbreiteten Dateipackers gzip. Dessen Funktionalität findet sich in der zlib/PHP-Funktion gzencode() wieder, zu der es aber merkwürdigerweise kein direktes Gegenstück zu geben scheint.
Das gzip-Dateiformat entspricht nun aber IIRC dem, was Du mit gzinflate() erreichst plus einiger Zusatzdaten wie Prüfsumme, Dateiname und -größe. Anders ausgedrückt entspricht gzencode() also gzinflate() mit ein paar Schokostreuseln obendrauf.
Das exakte gzip-Dateiformat findest Du unter http://www.ietf.org/rfc/rfc1952.txt wieder (so steht's übrigens auch in der Anleitungsseite zu gzencode()); mit den String-Funktionen sollten sich Deine Rohdaten passend zurecht stutzen lassen.
Falls es ab und an zu Dekodierungsfehlern kommt, nicht wundern: PHP hat (oder hatte?) hier und da Probleme mit Binärzeichenketten.
Beachte bitte auch, daß der Webserver Dir seine Daten -unabhängig von der gzip-Kodierung- häppchenweise schickt ("chunked"). Statt sofort der Daten bekommst Du erst die Länge in Bytes als Zeichenkette, einen Zeilenvorschub und dann ein entsprechend langes Datenstück. Abgeschlossen wird die Übertragung durch ein Null Bytes langes Stück oder Abbruch der Verbindung.
Ich bin mir nicht sicher, ob das so richtig ist, schaue bitte in der RFC 2616 (<[link:http://www.ietf.org/rfc/rfc2616.txt>]) nach, der Spezifikation von HTTP 1.1.
Gruß,
soenk.e
Vielen Dank. Hab' es hinbekommen.
Neelhiks
Moin!
HTTP/1.1 200 OK Date: Sun, 18 May 2003 13:00:17 GMT Server: Apache/1.3.26 (Unix) mod_gzip/1.3.19.1a PHP/4.2.2 mod_gzip/1.3.19.1a mod_fastcgi/2.2.12 mod_perl/1.27 mod_ssl/2.8.10 OpenSSL/0.9.6b X-Powered-By: PHP/4.2.2 X-Accelerated-By: PHPA/1.3.3r1 Expires: Thu, 19 Nov 1981 08:52:00 GMT Last-Modified: Sun, 18 May 2003 13:00:17 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Content-Encoding: gzip Connection: close Transfer-Encoding: chunked Content-Type: text/html
Oha. Wenn du HTTP/1.1 sprechen willst, dann hast du dir einiges vorgenommen.
Die Problematik hier dürfte unter anderem das Transfer-Encodung: chunked sein, welches du erstmal auseinandernehmen mußt. Danach kannst du dann das gzippen rückgängig machen. Darauf aber keine Garantie.
Wenn du es dir einfacher machen willst, sprichst du mit dem Server nur HTTP/1.0 und verzichtest auf gzip-Encoding. Ist dann natürlich mit mehr Datenmenge verbunden.
Jedenfalls solltest du dich informieren, wie HTTP im Detail funktioniert. Einfach nur einen String durch gunzip zu schleusen bringts nicht unbedingt.
- Sven Rautenberg
Hi Sven, hi Neelhiks,
Die Problematik hier dürfte unter anderem das Transfer-Encodung: chunked sein, welches du erstmal auseinandernehmen mußt. Danach kannst du dann das gzippen rückgängig machen. Darauf aber keine Garantie.
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.
Jedenfalls solltest du dich informieren, wie HTTP im Detail funktioniert. Einfach nur einen String durch gunzip zu schleusen bringts nicht unbedingt.
Das eigentliche Dekodieren ist wirklich nicht mehr.
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)
Gruss,
Carsten
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().