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