/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "zipfile.hh" #include #include namespace ZipFile { #pragma pack( push, 1 ) /// End-of-central-directory record, as is struct EndOfCdirRecord { quint32 signature; quint16 numDisk, numDiskCd, totalEntriesDisk, totalEntries; quint32 size, offset; quint16 commentLength; } #ifndef _MSC_VER __attribute__((packed)) #endif ; struct CentralFileHeaderRecord { quint32 signature; quint16 verMadeBy, verNeeded, gpBits, compressionMethod, fileTime, fileDate; quint32 crc32, compressedSize, uncompressedSize; quint16 fileNameLength, extraFieldLength, fileCommentLength, diskNumberStart, intFileAttrs; quint32 externalFileAttrs, offsetOfLocalHeader; } #ifndef _MSC_VER __attribute__((packed)) #endif ; struct LocalFileHeaderRecord { quint32 signature; quint16 verNeeded, gpBits, compressionMethod, fileTime, fileDate; quint32 crc32, compressedSize, uncompressedSize; quint16 fileNameLength, extraFieldLength; } #ifndef _MSC_VER __attribute__((packed)) #endif ; #pragma pack( pop ) static quint32 const endOfCdirRecordSignatureValue = qToLittleEndian( 0x06054b50 ); static quint32 const centralFileHeaderSignature = qToLittleEndian( 0x02014b50 ); static quint32 const localFileHeaderSignature = qToLittleEndian( 0x04034b50 ); static CompressionMethod getCompressionMethod( quint16 compressionMethod ) { switch( qFromLittleEndian( compressionMethod ) ) { case 0: return Uncompressed; case 8: return Deflated; default: return Unsupported; } } bool positionAtCentralDir( QFile & zip ) { // Find the end-of-central-directory record int maxEofBufferSize = 65535 + sizeof( EndOfCdirRecord ); if ( zip.size() > maxEofBufferSize ) zip.seek( zip.size() - maxEofBufferSize ); else if ( (size_t) zip.size() < sizeof( EndOfCdirRecord ) ) return false; else zip.seek( 0 ); QByteArray eocBuffer = zip.read( maxEofBufferSize ); if ( eocBuffer.size() < (int)sizeof( EndOfCdirRecord ) ) return false; int lastIndex = eocBuffer.size() - sizeof( EndOfCdirRecord ); QByteArray endOfCdirRecordSignature( (char const *)&endOfCdirRecordSignatureValue, sizeof( endOfCdirRecordSignatureValue ) ); EndOfCdirRecord endOfCdirRecord; for( ; ; --lastIndex ) { lastIndex = eocBuffer.lastIndexOf( endOfCdirRecordSignature, lastIndex ); if ( lastIndex == -1 ) return false; /// We need to copy it due to possible alignment issues on ARM etc memcpy( &endOfCdirRecord, eocBuffer.data() + lastIndex, sizeof( endOfCdirRecord ) ); /// Sanitize the record by checking the offset if ( !zip.seek( qFromLittleEndian( endOfCdirRecord.offset ) ) ) continue; quint32 signature; if ( zip.read( (char *)&signature, sizeof( signature ) ) != sizeof( signature ) ) continue; if ( signature == centralFileHeaderSignature ) break; } // Found cdir -- position the file on the first header return zip.seek( qFromLittleEndian( endOfCdirRecord.offset ) ); } bool readNextEntry( QFile & zip, CentralDirEntry & entry ) { CentralFileHeaderRecord record; if ( zip.read( (char *)&record, sizeof( record ) ) != sizeof( record ) ) return false; if ( record.signature != centralFileHeaderSignature ) return false; // Read file name int fileNameLength = qFromLittleEndian( record.fileNameLength ); entry.fileName = zip.read( fileNameLength ); if ( entry.fileName.size() != fileNameLength ) return false; // Skip extra fields if ( !zip.seek( ( zip.pos() + qFromLittleEndian( record.extraFieldLength ) ) + qFromLittleEndian( record.fileCommentLength ) ) ) return false; entry.localHeaderOffset = qFromLittleEndian( record.offsetOfLocalHeader ); entry.compressedSize = qFromLittleEndian( record.compressedSize ); entry.uncompressedSize = qFromLittleEndian( record.uncompressedSize ); entry.compressionMethod = getCompressionMethod( record.compressionMethod ); entry.fileNameInUTF8 = ( qFromLittleEndian( record.gpBits ) & 0x800 ) != 0; return true; } bool readLocalHeader( QFile & zip, LocalFileHeader & entry ) { LocalFileHeaderRecord record; if ( zip.read( (char *)&record, sizeof( record ) ) != sizeof( record ) ) return false; if ( record.signature != localFileHeaderSignature ) return false; // Read file name int fileNameLength = qFromLittleEndian( record.fileNameLength ); entry.fileName = zip.read( fileNameLength ); if ( entry.fileName.size() != fileNameLength ) return false; // Skip extra field if ( !zip.seek( zip.pos() + qFromLittleEndian( record.extraFieldLength ) ) ) return false; entry.compressedSize = qFromLittleEndian( record.compressedSize ); entry.uncompressedSize = qFromLittleEndian( record.uncompressedSize ); entry.compressionMethod = getCompressionMethod( record.compressionMethod ); return true; } }