function testInt32HashingSanity() { /* Max value for a signed 32-bit integer. */ $integerMax = 2147483647; $maxValue = $integerMax - HashUtility::HASH_OFFSET; $integersToTest = array(0, 1, 2, 3, 4, 5, 10, 21, 30, 43, 50, 101, 211, 300, 461, 500, 1011, 2000, 3000, 4051, 4096, 1000000000, 2000000000, $maxValue); $integersToTestNegative = array(1, 2, 3, 4, 5, 10, 21, 30, 43, 50, 101, 211, 300, 461, 500, 1011, 2000, 3000, 4051, 4096, 1000000000, 2000000000, $maxValue, $integerMax, $integerMax + HashUtility::HASH_OFFSET); foreach ($integersToTest as $integer) { $hash = HashUtility::hashInt32($integer); $unhashed = HashUtility::unhashInt32($hash); $this->assertEqual($integer, $unhashed, sprintf("HashUtility::hashInt32(%s) should equal HashUtility::unhashInt32(%s), equals %s", $integer, $hash, $unhashed)); } foreach ($integersToTestNegative as $integer) { $integer *= -1; $hash = HashUtility::hashInt32($integer); $unhashed = HashUtility::unhashInt32($hash); $this->assertEqual($integer, $unhashed, sprintf("HashUtility::hashInt32(%s) should equal HashUtility::unhashInt32(%s), equals %s", $integer, $hash, $unhashed)); } }
/** * Adds a file to the archive. * * @param string Name of file as to be stored inside the archive, * optionally including the path. * @param string File data. * @param integer Last modified timestamp, or false for current time. * @return void */ function addFileFromDisk($name, $filename, $timestamp = false) { /* Normally, we would split this into several methods, but this must be * optimized for minimal RAM usage, which presents a design challenge. */ $data = @file_get_contents($filename); /* Do we still have a valid file handle? */ if (!$this->_fileHandle) { $this->_errorMessage = 'Unexpected end of file.'; return false; } /* Convert DOS / Windows paths to UNIX paths. */ $name = str_replace('\\', '/', $name); /* If a timestamp wasn't specified, use the current time. */ if ($timestamp === false) { $timestamp = time(); } /* Convert our UNIX timestamp to DOS format. */ $DOSTime = FileCompressorUtility::UNIXToDOSTime($timestamp); /* Calculate the length of the file data before compression. */ $uncompressedLength = filesize($filename); /* Calculate the CRC32 checksum of the file data to be compressed. */ $CRC32 = HashUtility::crc32File($filename); /* Version needed to extract. * * Store Only: 10 * DEFLATE: 20 * BZIP2: 46 * * From the ZIP format specification: * * Current minimum feature versions are as defined below: * * 1.0 - Default value * 1.1 - File is a volume label * 2.0 - File is a folder (directory) * 2.0 - File is compressed using Deflate compression * 2.0 - File is encrypted using traditional PKWARE encryption * 2.1 - File is compressed using Deflate64(tm) * 2.5 - File is compressed using PKWARE DCL Implode * 2.7 - File is a patch data set * 4.5 - File uses ZIP64 format extensions * 4.6 - File is compressed using BZIP2 compression* * 5.0 - File is encrypted using DES * 5.0 - File is encrypted using 3DES * 5.0 - File is encrypted using original RC2 encryption * 5.0 - File is encrypted using RC4 encryption * 5.1 - File is encrypted using AES encryption * 5.1 - File is encrypted using corrected RC2 encryption** * 5.2 - File is encrypted using corrected RC2-64 encryption** * 6.1 - File is encrypted using non-OAEP key wrapping*** * 6.2 - Central directory encryption * 6.3 - File is compressed using LZMA * 6.3 - File is compressed using PPMd+ * 6.3 - File is encrypted using Blowfish * 6.3 - File is encrypted using Twofish */ $versionNeededToExtract = 20; /* General purpose bit-flag. * * From the ZIP format specification: * * Bit 0: If set, indicates that the file is encrypted. * * <cut, irrelevant> * * (For Methods 8 and 9 - Deflating) * Bit 2 Bit 1 * 0 0 Normal (-en) compression option was used. * 0 1 Maximum (-exx/-ex) compression option was used. * 1 0 Fast (-ef) compression option was used. * 1 1 Super Fast (-es) compression option was used. * * <cut, irrelevant> * * Bit 3: If this bit is set, the fields crc-32, compressed * size and uncompressed size are set to zero in the * local header. The correct values are put in the * data descriptor immediately following the compressed * data. (Note: PKZIP version 2.04g for DOS only * recognizes this bit for method 8 compression, newer * versions of PKZIP recognize this bit for any * compression method.) * * Bit 4: Reserved for use with method 8, for enhanced * deflating. * * Bit 5: If this bit is set, this indicates that the file is * compressed patched data. (Note: Requires PKZIP * version 2.70 or greater) * * Bit 6: Strong encryption. If this bit is set, you should * set the version needed to extract value to at least * 50 and you must also set bit 0. If AES encryption * is used, the version needed to extract value must * be at least 51. * * Bit [7-10]: Currently unused. * * Bit 11: Language encoding flag (EFS). If this bit is set, * the filename and comment fields for this file * must be encoded using UTF-8. (see APPENDIX D) * * Bit 12: Reserved by PKWARE for enhanced compression. * * Bit 13: Used when encrypting the Central Directory to indicate * selected data values in the Local Header are masked to * hide their actual values. See the section describing * the Strong Encryption Specification for details. * * Bit [14-15]: Reserved by PKWARE. */ $generalPurposeBitFlag = 0; /* Compression method. * * 0: STORE * 8: DEFLATE * 12: BZIP2 */ $compressionMethod = 8; /* Extra field length. */ $extraFieldLength = 0; /* Format: * * [4B] [Start of File Record Marker] * [2B] [Version Needed to Extract] * [2B] [General Purpose Bit Flag (See Above)] * [2B] [Compression Method (See Above)] * * [4B] [Last-Modified Timestamp in DOS Format] * [4B] [CRC32 Checksum of Compressed Data] * [4B] [Compressed Data Length] * [4B] [Uncompressed Data Length] * * [2B] [Filename Length] * [2B] [Extra Field Length] */ $fileRecord = pack('VvvvVVVVvv', START_FILE_RECORD, $versionNeededToExtract, $generalPurposeBitFlag, $compressionMethod, $DOSTime, $CRC32, 0, $uncompressedLength, strlen($name), $extraFieldLength); /* Filename. */ $fileRecord .= $name; /* This is the "data descriptor" section, however, apparently this * causes problems and is optional anyway. * * From the ZIP format specification: * C. Data descriptor: * * crc-32 4 bytes * compressed size 4 bytes * uncompressed size 4 bytes * * This descriptor exists only if bit 3 of the general * purpose bit flag is set (see below). It is byte aligned * and immediately follows the last byte of compressed data. * This descriptor is used only when it was not possible to * seek in the output .ZIP file, e.g., when the output .ZIP file * was standard output or a non-seekable device. For ZIP64(tm) format * archives, the compressed and uncompressed sizes are 8 bytes each. * * When compressing files, compressed and uncompressed sizes * should be stored in ZIP64 format (as 8 byte values) when a * files size exceeds 0xFFFFFFFF. However ZIP64 format may be * used regardless of the size of a file. When extracting, if * the zip64 extended information extra field is present for * the file the compressed and uncompressed sizes will be 8 * byte values. * * Although not originally assigned a signature, the value * 0x08074b50 has commonly been adopted as a signature value * for the data descriptor record. Implementers should be * aware that ZIP files may be encountered with or without this * signature marking data descriptors and should account for * either case when reading ZIP files to ensure compatibility. * When writing ZIP files, it is recommended to include the * signature value marking the data descriptor record. When * the signature is used, the fields currently defined for * the data descriptor record will immediately follow the * signature. * * <cut, irrlevent> * * When the Central Directory Encryption method is used, the data * descriptor record is not required, but may be used. If present, * and bit 3 of the general purpose bit field is set to indicate * its presence, the values in fields of the data descriptor * record should be set to binary zeros. * * $fileRecord .= pack('V', START_DATA_DESCRIPTOR); * $fileRecord .= pack('V', $CRC32); * $fileRecord .= pack('V', $compressedLength); * $fileRecord .= pack('V', $uncompressedLength); */ /* Get the length of this file record for use later on. */ $fileRecordLength = strlen($fileRecord); /* Add this file record to the zip file. */ if (fwrite($this->_fileHandle, $fileRecord) === false) { return false; } unset($fileRecord); $tempFilename = FileUtility::makeRandomTemporaryFilePath(); $compressedLength = 0; $fhSource = fopen(realpath($filename), 'rb'); $fhCompressor = gzopen($tempFilename, 'wb'); if (!$fhSource or !$fhCompressor) { $this->_errorMessage = 'Unexpected end of file.'; return false; } while (!feof($fhSource)) { $temp = fread($fhSource, 32767); gzwrite($fhCompressor, $temp); } gzclose($fhCompressor); fclose($fhSource); $fhCompressed = fopen($tempFilename, 'rb'); if (!$fhCompressed) { $this->_errorMessage = 'Unexpected end of file.'; return false; } /* Strip off the headers and footers. */ $gzipHeaderLength = 10; $gzipFooterLength = 8; if (fseek($fhCompressed, $gzipHeaderLength, SEEK_SET) === -1) { $this->_errorMessage = 'Unexpected end of file.'; return false; } while (!feof($fhCompressed)) { $temp = fread($fhCompressed, 8192); $compressedLength += strlen($temp); fwrite($this->_fileHandle, $temp); } fclose($fhCompressed); @unlink($tempFilename); /* Ignore last 8 bytes of compressed data. */ $compressedLength -= $gzipFooterLength; /* We need to seek back into the file and correct the 4B representation of how large the compressed data is. It is stored at pointer - $compressedSize - 12 bytes. */ if (fseek($this->_fileHandle, 0 - $compressedLength - strlen($name) - $gzipFooterLength - 12, SEEK_CUR) === -1) { $this->_errorMessage = 'Unexpected end of file.'; return false; } $compressedLengthPacked = pack('V', $compressedLength); fwrite($this->_fileHandle, $compressedLengthPacked); /* Seek to the end of the file, but seek back 4 bytes to recover CRC bug for gzcompress. */ if (fseek($this->_fileHandle, 0 - $gzipFooterLength, SEEK_END) === -1) { $this->_errorMessage = 'Unexpected end of file.'; return false; } /* Increment total compressed data length and file record count. */ $fileRecordLength += $compressedLength; $this->_fileRecordsLength += $fileRecordLength; ++$this->_fileRecordCount; /* Create the Central Directory entry for this file and append it * to the Central Directory (stored in memory for now until all file * records have been written). */ $this->createCentralDirectoryEntry($name, $DOSTime, $CRC32, $compressedLength, $uncompressedLength, $fileRecordLength); return true; }