1 /* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org>
2 * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
3
4 #include "zipfile.hh"
5 #include <QtEndian>
6 #include <QByteArray>
7 #include <QFileInfo>
8
9 namespace ZipFile {
10
11 #pragma pack( push, 1 )
12
13 /// End-of-central-directory record, as is
14 struct EndOfCdirRecord
15 {
16 quint32 signature;
17 quint16 numDisk, numDiskCd, totalEntriesDisk, totalEntries;
18 quint32 size, offset;
19 quint16 commentLength;
20 }
21 #ifndef _MSC_VER
22 __attribute__((packed))
23 #endif
24 ;
25
26 struct CentralFileHeaderRecord
27 {
28 quint32 signature;
29 quint16 verMadeBy, verNeeded, gpBits, compressionMethod, fileTime, fileDate;
30 quint32 crc32, compressedSize, uncompressedSize;
31 quint16 fileNameLength, extraFieldLength, fileCommentLength, diskNumberStart,
32 intFileAttrs;
33 quint32 externalFileAttrs, offsetOfLocalHeader;
34 }
35 #ifndef _MSC_VER
36 __attribute__((packed))
37 #endif
38 ;
39
40 struct LocalFileHeaderRecord
41 {
42 quint32 signature;
43 quint16 verNeeded, gpBits, compressionMethod, fileTime, fileDate;
44 quint32 crc32, compressedSize, uncompressedSize;
45 quint16 fileNameLength, extraFieldLength;
46 }
47 #ifndef _MSC_VER
48 __attribute__((packed))
49 #endif
50 ;
51
52 #pragma pack( pop )
53
54 static quint32 const endOfCdirRecordSignatureValue = qToLittleEndian( 0x06054b50 );
55 static quint32 const centralFileHeaderSignature = qToLittleEndian( 0x02014b50 );
56 static quint32 const localFileHeaderSignature = qToLittleEndian( 0x04034b50 );
57
getCompressionMethod(quint16 compressionMethod)58 static CompressionMethod getCompressionMethod( quint16 compressionMethod )
59 {
60 switch( qFromLittleEndian( compressionMethod ) )
61 {
62 case 0:
63 return Uncompressed;
64 case 8:
65 return Deflated;
66 default:
67 return Unsupported;
68 }
69 }
70
positionAtCentralDir(SplitZipFile & zip)71 bool positionAtCentralDir( SplitZipFile & zip )
72 {
73 // Find the end-of-central-directory record
74
75 int maxEofBufferSize = 65535 + sizeof( EndOfCdirRecord );
76
77 if ( zip.size() > maxEofBufferSize )
78 zip.seek( zip.size() - maxEofBufferSize );
79 else
80 if ( (size_t) zip.size() < sizeof( EndOfCdirRecord ) )
81 return false;
82 else
83 zip.seek( 0 );
84
85 QByteArray eocBuffer = zip.read( maxEofBufferSize );
86
87 if ( eocBuffer.size() < (int)sizeof( EndOfCdirRecord ) )
88 return false;
89
90 int lastIndex = eocBuffer.size() - sizeof( EndOfCdirRecord );
91
92 QByteArray endOfCdirRecordSignature( (char const *)&endOfCdirRecordSignatureValue,
93 sizeof( endOfCdirRecordSignatureValue ) );
94
95 EndOfCdirRecord endOfCdirRecord;
96
97 quint32 cdir_offset;
98
99 for( ; ; --lastIndex )
100 {
101 lastIndex = eocBuffer.lastIndexOf( endOfCdirRecordSignature, lastIndex );
102
103 if ( lastIndex == -1 )
104 return false;
105
106 /// We need to copy it due to possible alignment issues on ARM etc
107 memcpy( &endOfCdirRecord, eocBuffer.data() + lastIndex,
108 sizeof( endOfCdirRecord ) );
109
110 /// Sanitize the record by checking the offset
111
112 cdir_offset = zip.calcAbsoluteOffset( qFromLittleEndian( endOfCdirRecord.offset ),
113 qFromLittleEndian( endOfCdirRecord.numDiskCd ) );
114
115 if ( !zip.seek( cdir_offset ) )
116 continue;
117
118 quint32 signature;
119
120 if ( zip.read( (char *)&signature, sizeof( signature ) ) != sizeof( signature ) )
121 continue;
122
123 if ( signature == centralFileHeaderSignature )
124 break;
125 }
126
127 // Found cdir -- position the file on the first header
128
129 return zip.seek( cdir_offset );
130 }
131
readNextEntry(SplitZipFile & zip,CentralDirEntry & entry)132 bool readNextEntry( SplitZipFile & zip, CentralDirEntry & entry )
133 {
134 CentralFileHeaderRecord record;
135
136 if ( zip.read( (char *)&record, sizeof( record ) ) != sizeof( record ) )
137 return false;
138
139 if ( record.signature != centralFileHeaderSignature )
140 return false;
141
142 // Read file name
143
144 int fileNameLength = qFromLittleEndian( record.fileNameLength );
145 entry.fileName = zip.read( fileNameLength );
146
147 if ( entry.fileName.size() != fileNameLength )
148 return false;
149
150 // Skip extra fields
151
152 if ( !zip.seek( ( zip.pos() + qFromLittleEndian( record.extraFieldLength ) ) +
153 qFromLittleEndian( record.fileCommentLength ) ) )
154 return false;
155
156 entry.localHeaderOffset = zip.calcAbsoluteOffset( qFromLittleEndian( record.offsetOfLocalHeader ),
157 qFromLittleEndian( record.diskNumberStart ) );
158 entry.compressedSize = qFromLittleEndian( record.compressedSize );
159 entry.uncompressedSize = qFromLittleEndian( record.uncompressedSize );
160 entry.compressionMethod = getCompressionMethod( record.compressionMethod );
161 entry.fileNameInUTF8 = ( qFromLittleEndian( record.gpBits ) & 0x800 ) != 0;
162
163 return true;
164 }
165
readLocalHeader(SplitZipFile & zip,LocalFileHeader & entry)166 bool readLocalHeader( SplitZipFile & zip, LocalFileHeader & entry )
167 {
168 LocalFileHeaderRecord record;
169
170 if ( zip.read( (char *)&record, sizeof( record ) ) != sizeof( record ) )
171 return false;
172
173 if ( record.signature != localFileHeaderSignature )
174 return false;
175
176 // Read file name
177
178 int fileNameLength = qFromLittleEndian( record.fileNameLength );
179 entry.fileName = zip.read( fileNameLength );
180
181 if ( entry.fileName.size() != fileNameLength )
182 return false;
183
184 // Skip extra field
185
186 if ( !zip.seek( zip.pos() + qFromLittleEndian( record.extraFieldLength ) ) )
187 return false;
188
189 entry.compressedSize = qFromLittleEndian( record.compressedSize );
190 entry.uncompressedSize = qFromLittleEndian( record.uncompressedSize );
191 entry.compressionMethod = getCompressionMethod( record.compressionMethod );
192
193 return true;
194 }
195
SplitZipFile(const QString & name)196 SplitZipFile::SplitZipFile( const QString & name )
197 {
198 setFileName( name );
199 }
200
setFileName(const QString & name)201 void SplitZipFile::setFileName( const QString & name )
202 {
203 {
204 QString lname = name.toLower();
205 if( lname.endsWith( ".zips" ) )
206 {
207 appendFile( name );
208 return;
209 }
210
211 if( !lname.endsWith( ".zip" ) )
212 return;
213 }
214
215 if( QFileInfo( name ).isFile() )
216 {
217 for( int i = 1; i < 100; i++ )
218 {
219 QString name2 = name.left( name.size() - 2 ) + QString( "%1" ).arg( i, 2, 10, QChar( '0' ) );
220 if( QFileInfo( name2 ).isFile() )
221 appendFile( name2 );
222 else
223 break;
224 }
225 appendFile( name );
226 }
227 else
228 {
229 for( int i = 1; i < 1000; i++ )
230 {
231 QString name2 = name + QString( ".%1" ).arg( i, 3, 10, QChar( '0' ) );
232 if( QFileInfo( name2 ).isFile() )
233 appendFile( name2 );
234 else
235 break;
236 }
237 }
238 }
239
lastModified() const240 QDateTime SplitZipFile::lastModified() const
241 {
242 unsigned long ts = 0;
243 for( QVector< QFile * >::const_iterator i = files.begin(); i != files.end(); ++i )
244 {
245 unsigned long t = QFileInfo( (*i)->fileName() ).lastModified().toTime_t();
246 if( t > ts )
247 ts = t;
248 }
249 return QDateTime::fromTime_t( ts );
250 }
251
calcAbsoluteOffset(qint64 offset,quint16 partNo)252 qint64 SplitZipFile::calcAbsoluteOffset( qint64 offset, quint16 partNo )
253 {
254 if( partNo >= offsets.size() )
255 return 0;
256
257 return offsets.at( partNo ) + offset;
258 }
259
260 }
261