PHP is a language that has largely had its development driven by the real world use and demands by its users. Those users, unfortunately, have not always updated in a timely fashion, so functionality that has been implemented over multiple versions is not always fully available and can create problems for the unaware.
The zlib family of functions are one such animal. Despite it technically being an optional library, it’s virtually always included and most of the functions have been available since PHP 4. There’s a notable exception though: gzdecode
, the complement for gzencode
. It was not added into PHP’s zlib integration with the rest 1 — it came much later with PHP 5.4.
Workaround
Fortunately, it’s possible to achieve the functionality of gzdecode
with gzinflate
:
if (!function_exists('gzdecode')) { function gzdecode($data, $length = 0) //Based on https://www.ietf.org/rfc/rfc1952.txt { //Ensure the data meets the minimum length for header, checksum, and footer $length = strlen($data); if ($length < 18) { return null; } $header_length = 10; extract(unpack('nmagic_bytes/Cmethod/Cflags/Vmtime/Cxfl/Cos', $data)); if ($magic_bytes !== 0x1f8b) { return null; } //Ensure it starts with GZIP's magic bytes if ($method !== 8) { return null; } //Only deflate is supported if (($flags & 0x1f) !== $flags) { return null; } //Reserved bits should not be set $extra_length = 0; $extra = ''; if ($flags & 4) //FEXTRA is set, 2-byte length + data { if ($length - $header_length - 2 < 8) { return null; } extract(unpack('vextra_length', substr($data, 8, 2))); if ($length - $header_length - 2 - $extra_length < 8) { return null; } $extra = substr($data, 10, $extra_length); $header_length += 2 + $extra_length; } $filename_length = 0; $filename = ''; if ($flags & 8) //FNAME is set, null-terminated C-string { if ($length - $header_length - 1 < 8) { return null; } $filename_length = strpos(substr($data, $header_length), chr(0)); if ($filename_length === false || $length - $header_length - $filename_length - 1 < 8) { return null; } $filename = substr($data, $header_length, $filename_length); $header_length += $filename_length + 1; } $comment_length = 0; $comment = ''; if ($flags & 16) //FCOMMENT is set, null-terminated C-string { if ($length - $header_length - 1 < 8) { return null; } $comment_length = strpos(substr($data, $header_length), chr(0)); if ($comment_length === false || $length - $header_length - $comment_length - 1 < 8) { return null; } $comment = substr($data, $header_length, $comment_length); $header_length += $comment_length + 1; } $header_crc16 = ''; if ($flags & 2) //FHCRC is set, 2-byte CRC16 { /* RFC: If FHCRC is set, a CRC16 for the gzip header is present, immediately before the compressed data. The CRC16 consists of the two least significant bytes of the CRC32 for all bytes of the gzip header up to and not including the CRC16. */ if ($length - $header_length - 2 < 8) { return null; } $crc16 = crc32(substr($data, 0, $header_length)) & 0xffff; extract(unpack('vheader_crc16', substr($data, $header_length, 2))); if ($header_crc16 !== $crc16) { return null; } $header_length += 2; } //Decompress $body_length = $length - $header_length - 8; if ($body_length < 0) { return null; } $body = substr($data, $header_length, $body_length); $decompressed_body = ''; if ($body_length > 0) { $decompressed_body = gzinflate($body, $length); if ($decompressed_body === false) { return null; } } //Verify CRC32 extract(unpack('Vdata_crc32/Visize', substr($data, -8))); $crc32 = sprintf("%u", crc32($decompressed_body)); //From docs: Because PHP's integer type is signed many crc32 checksums will result in negative integers on 32bit platforms. On 64bit installations all crc32() results will be positive integers though. So you need to use the "%u" formatter of sprintf() or printf() to get the string representation of the unsigned crc32() checksum in decimal format. if ($crc32 != $data_crc32 || strlen($decompressed_body) != $isize) { return null; } return $decompressed_body; } }