views:

663

answers:

3

I'm using a combination of Paul Duncans php ZipStream (http://pablotron.org/software/zipstream-php/) on the server side, for on-the-fly creation of zips, and Fzip (http://codeazur.com.br/lab/fzip/) on the Flex/Air client side.

Works fine in Air, but when running Flex in browser, the zip needs to include a Adler32 checksum in the header for FZip to be read.

How can I calculate an Adler32 checksum for the zip in php?

The ZipStream core functions, using gzdeflate for compression, can be seen below.

Regards / Jonas

function add_file($name, $data, $opt = array(), $deflateLevel=0) {
 # compress data

 $zdata = gzdeflate($data, $deflateLevel);

 # calculate header attributes

 $crc  = crc32($data);
 $zlen = strlen($zdata);
 $len  = strlen($data);
 $meth = 0x08;

 # send file header
 $this->add_file_header($name, $opt, $meth, $crc, $zlen, $len);

 # print data
 $this->send($zdata);
}

private function add_file_header($name, $opt, $meth, $crc, $zlen, $len) {
 # strip leading slashes from file name
 # (fixes bug in windows archive viewer)
 $name = preg_replace('/^\\/+/', '', $name);

 # calculate name length
 $nlen = strlen($name);

 # create dos timestamp
 $opt['time'] = $opt['time'] ? $opt['time'] : time();
 $dts = $this->dostime($opt['time']);

 # build file header
 $fields = array(            # (from V.A of APPNOTE.TXT)
 array('V', 0x04034b50),     # local file header signature
 array('v', (6 << 8) + 3),   # version needed to extract
 array('v', 0x00),           # general purpose bit flag
 array('v', $meth),          # compresion method (deflate or store)
 array('V', $dts),           # dos timestamp
 array('V', $crc),           # crc32 of data
 array('V', $zlen),          # compressed data length
 array('V', $len),           # uncompressed data length
 array('v', $nlen),          # filename length
 array('v', 0),              # extra data len
 );

 # pack fields and calculate "total" length
 $ret = $this->pack_fields($fields);
 $cdr_len = strlen($ret) + $nlen + $zlen;

 # print header and filename
 $this->send($ret . $name);

 # add to central directory record and increment offset
 $this->add_to_cdr($name, $opt, $meth, $crc, $zlen, $len, $cdr_len);
}
+1  A: 

Tanslated from the example implementation in the Wikipedia article:

define('MOD_ADLER', 65521);

function adler32($data) {
    $a = 1; $b = 0; $len = strlen($data);
    for ($index = 0; $index < $len; ++$index) {
        $a = ($a + $data[$index]) % MOD_ADLER;
        $b = ($b + $a) % MOD_ADLER;
    }
    return ($b << 16) | $a;
}

And to convert that integer value to bytes:

pack('H*', $checksum);
Gumbo
Thanx, Gumbo!I'll test it!Jonas
Cambiata
+2  A: 

For PHP 5 >= 5.1.2 you can use the hash function which returns a hex string representation of the crc:

$dataStr = "abc";
$crcStr = hash('adler32', $dataStr);

UPDATE: There was a bug in hash until mid 2009 where the byte order was around the wrong way. The bug appears to fixed in 5.2.11 and 5.3.0 onwards. But for earlier versions the byte order of the result will need to be swapped.

UPDATE 2: Here's a wrapper function (and a test) which could be used to work around that bug:

<?php

error_reporting(E_ALL);

function hash_adler32_wrapper($data) {
    $digHexStr = hash("adler32", $data);

    // If version is better than 5.2.11 no further action necessary
    if (version_compare(PHP_VERSION, '5.2.11', '>=')) {
        return $digHexStr;
    }

    // Workaround #48284 by swapping byte order
    $boFixed = array();
    $boFixed[0] = $digHexStr[6];
    $boFixed[1] = $digHexStr[7];
    $boFixed[2] = $digHexStr[4];
    $boFixed[3] = $digHexStr[5];
    $boFixed[4] = $digHexStr[2];
    $boFixed[5] = $digHexStr[3];
    $boFixed[6] = $digHexStr[0];
    $boFixed[7] = $digHexStr[1];

    return implode("", $boFixed);
}

// Test fixture, plus expected output generated using the adler32 from zlib
$data_in = "abc";
$expected_out = 0x024d0127;

// PHP's hash function returns a hex hash value as a string so hexdec used to
// convert to number
$hash_out = hexdec(hash("adler32", $data_in));

// Get value via the wrapper function
$wrapper_out = hexdec(hash_adler32_wrapper($data_in));

printf("data_in:          %s\n", $data_in);
printf("expected_out:     0x%08x\n", $expected_out);
printf("builtin hash out: 0x%08x, %s\n", $hash_out,
        ($hash_out == $expected_out)? "OK" : "NOT OK");
printf("wrapper func out: 0x%08x, %s\n", $wrapper_out,
        ($wrapper_out == $expected_out)? "OK" : "NOT OK");

?>
MunckyMagik
+1  A: 

Gumbo's solution is almost perfect - but if the parameter is a string instead of an array of bytes, you'll need to use ord() to get the ascii code of the character to process. Like this:

function adler32($data) {
    $a = 1; $b = 0; $len = strlen($data);
    for ($index = 0; $index < $len; ++$index) {
        $a = ($a + ord($data[$index])) % 65521;
        $b = ($b + $a) % 65521;
    }
    return ($b << 16) | $a;
}
Tick