1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
/* This file is (c) 2008-2010 Konstantin Isakov <ikm@users.berlios.de>
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "zipfile.hh"
#include <QtEndian>
#include <QByteArray>
namespace ZipFile {
/// End-of-central-directory record, as is
struct EndOfCdirRecord
{
quint32 signature;
quint16 numDisk, numDiskCd, totalEntriesDisk, totalEntries;
quint32 size, offset;
quint16 commentLength;
} __attribute__((packed));
struct CentralFileHeaderRecord
{
quint32 signature;
quint16 verMadeBy, verNeeded, gpBits, compressionMethod, fileTime, fileDate;
quint32 crc32, compressedSize, uncompressedSize;
quint16 fileNameLength, extraFieldLength, fileCommentLength, diskNumberStart,
intFileAttrs;
quint32 externalFileAttrs, offsetOfLocalHeader;
} __attribute__((packed));
struct LocalFileHeaderRecord
{
quint32 signature;
quint16 verNeeded, gpBits, compressionMethod, fileTime, fileDate;
quint32 crc32, compressedSize, uncompressedSize;
quint16 fileNameLength, extraFieldLength;
} __attribute__((packed));
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 ( 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 );
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;
}
}
|