diff options
author | Dominik Riebeling <Dominik.Riebeling@gmail.com> | 2007-07-25 20:21:06 +0000 |
---|---|---|
committer | Dominik Riebeling <Dominik.Riebeling@gmail.com> | 2007-07-25 20:21:06 +0000 |
commit | 680408f4ac8b04d716fd8921c01b73d30a3c66c7 (patch) | |
tree | 08908a7d5e3b95daa9a1d3ed0703e64cff712022 /rbutil/rbutilqt/zip/zip.cpp | |
parent | aa643db434e230643e18e454032fb708e0f1848d (diff) | |
download | rockbox-680408f4ac8b04d716fd8921c01b73d30a3c66c7.tar.gz rockbox-680408f4ac8b04d716fd8921c01b73d30a3c66c7.zip |
First stab at porting rbutil to Qt4. Currently only installing a current or archived build is working. To build, run qmake && make in the source folder. Beware that the syntax of rbutil.ini has slightly changed. Caching of the downloaded files is also still missing.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13989 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'rbutil/rbutilqt/zip/zip.cpp')
-rw-r--r-- | rbutil/rbutilqt/zip/zip.cpp | 1219 |
1 files changed, 1219 insertions, 0 deletions
diff --git a/rbutil/rbutilqt/zip/zip.cpp b/rbutil/rbutilqt/zip/zip.cpp new file mode 100644 index 0000000000..bb02147da3 --- /dev/null +++ b/rbutil/rbutilqt/zip/zip.cpp | |||
@@ -0,0 +1,1219 @@ | |||
1 | /**************************************************************************** | ||
2 | ** Filename: zip.cpp | ||
3 | ** Last updated [dd/mm/yyyy]: 01/02/2007 | ||
4 | ** | ||
5 | ** pkzip 2.0 file compression. | ||
6 | ** | ||
7 | ** Some of the code has been inspired by other open source projects, | ||
8 | ** (mainly Info-Zip and Gilles Vollant's minizip). | ||
9 | ** Compression and decompression actually uses the zlib library. | ||
10 | ** | ||
11 | ** Copyright (C) 2007 Angius Fabrizio. All rights reserved. | ||
12 | ** | ||
13 | ** This file is part of the OSDaB project (http://osdab.sourceforge.net/). | ||
14 | ** | ||
15 | ** This file may be distributed and/or modified under the terms of the | ||
16 | ** GNU General Public License version 2 as published by the Free Software | ||
17 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
18 | ** packaging of this file. | ||
19 | ** | ||
20 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
21 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
22 | ** | ||
23 | ** See the file LICENSE.GPL that came with this software distribution or | ||
24 | ** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. | ||
25 | ** | ||
26 | **********************************************************************/ | ||
27 | |||
28 | #include "zip.h" | ||
29 | #include "zip_p.h" | ||
30 | #include "zipentry_p.h" | ||
31 | |||
32 | // we only use this to seed the random number generator | ||
33 | #include <time.h> | ||
34 | |||
35 | #include <QMap> | ||
36 | #include <QString> | ||
37 | #include <QStringList> | ||
38 | #include <QDir> | ||
39 | #include <QFile> | ||
40 | #include <QDateTime> | ||
41 | #include <QCoreApplication> | ||
42 | |||
43 | // You can remove this #include if you replace the qDebug() statements. | ||
44 | #include <QtDebug> | ||
45 | |||
46 | //! Local header size (including signature, excluding variable length fields) | ||
47 | #define ZIP_LOCAL_HEADER_SIZE 30 | ||
48 | //! Encryption header size | ||
49 | #define ZIP_LOCAL_ENC_HEADER_SIZE 12 | ||
50 | //! Data descriptor size (signature included) | ||
51 | #define ZIP_DD_SIZE_WS 16 | ||
52 | //! Central Directory record size (signature included) | ||
53 | #define ZIP_CD_SIZE 46 | ||
54 | //! End of Central Directory record size (signature included) | ||
55 | #define ZIP_EOCD_SIZE 22 | ||
56 | |||
57 | // Some offsets inside a local header record (signature included) | ||
58 | #define ZIP_LH_OFF_VERS 4 | ||
59 | #define ZIP_LH_OFF_GPFLAG 6 | ||
60 | #define ZIP_LH_OFF_CMET 8 | ||
61 | #define ZIP_LH_OFF_MODT 10 | ||
62 | #define ZIP_LH_OFF_MODD 12 | ||
63 | #define ZIP_LH_OFF_CRC 14 | ||
64 | #define ZIP_LH_OFF_CSIZE 18 | ||
65 | #define ZIP_LH_OFF_USIZE 22 | ||
66 | #define ZIP_LH_OFF_NAMELEN 26 | ||
67 | #define ZIP_LH_OFF_XLEN 28 | ||
68 | |||
69 | // Some offsets inside a data descriptor record (including signature) | ||
70 | #define ZIP_DD_OFF_CRC32 4 | ||
71 | #define ZIP_DD_OFF_CSIZE 8 | ||
72 | #define ZIP_DD_OFF_USIZE 12 | ||
73 | |||
74 | // Some offsets inside a Central Directory record (including signature) | ||
75 | #define ZIP_CD_OFF_MADEBY 4 | ||
76 | #define ZIP_CD_OFF_VERSION 6 | ||
77 | #define ZIP_CD_OFF_GPFLAG 8 | ||
78 | #define ZIP_CD_OFF_CMET 10 | ||
79 | #define ZIP_CD_OFF_MODT 12 | ||
80 | #define ZIP_CD_OFF_MODD 14 | ||
81 | #define ZIP_CD_OFF_CRC 16 | ||
82 | #define ZIP_CD_OFF_CSIZE 20 | ||
83 | #define ZIP_CD_OFF_USIZE 24 | ||
84 | #define ZIP_CD_OFF_NAMELEN 28 | ||
85 | #define ZIP_CD_OFF_XLEN 30 | ||
86 | #define ZIP_CD_OFF_COMMLEN 32 | ||
87 | #define ZIP_CD_OFF_DISKSTART 34 | ||
88 | #define ZIP_CD_OFF_IATTR 36 | ||
89 | #define ZIP_CD_OFF_EATTR 38 | ||
90 | #define ZIP_CD_OFF_LHOFF 42 | ||
91 | |||
92 | // Some offsets inside a EOCD record (including signature) | ||
93 | #define ZIP_EOCD_OFF_DISKNUM 4 | ||
94 | #define ZIP_EOCD_OFF_CDDISKNUM 6 | ||
95 | #define ZIP_EOCD_OFF_ENTRIES 8 | ||
96 | #define ZIP_EOCD_OFF_CDENTRIES 10 | ||
97 | #define ZIP_EOCD_OFF_CDSIZE 12 | ||
98 | #define ZIP_EOCD_OFF_CDOFF 16 | ||
99 | #define ZIP_EOCD_OFF_COMMLEN 20 | ||
100 | |||
101 | //! PKZip version for archives created by this API | ||
102 | #define ZIP_VERSION 0x14 | ||
103 | |||
104 | //! Do not store very small files as the compression headers overhead would be to big | ||
105 | #define ZIP_COMPRESSION_THRESHOLD 60 | ||
106 | |||
107 | //! This macro updates a one-char-only CRC; it's the Info-Zip macro re-adapted | ||
108 | #define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) | ||
109 | |||
110 | /*! | ||
111 | \class Zip zip.h | ||
112 | |||
113 | \brief Zip file compression. | ||
114 | |||
115 | Some quick usage examples. | ||
116 | |||
117 | \verbatim | ||
118 | Suppose you have this directory structure: | ||
119 | |||
120 | /root/dir1/ | ||
121 | /root/dir1/file1.1 | ||
122 | /root/dir1/file1.2 | ||
123 | /root/dir1/dir1.1/ | ||
124 | /root/dir1/dir1.2/file1.2.1 | ||
125 | |||
126 | EXAMPLE 1: | ||
127 | myZipInstance.addDirectory("/root/dir1"); | ||
128 | |||
129 | RESULT: | ||
130 | Beheaves like any common zip software and creates a zip file with this structure: | ||
131 | |||
132 | dir1/ | ||
133 | dir1/file1.1 | ||
134 | dir1/file1.2 | ||
135 | dir1/dir1.1/ | ||
136 | dir1/dir1.2/file1.2.1 | ||
137 | |||
138 | EXAMPLE 2: | ||
139 | myZipInstance.addDirectory("/root/dir1", "myRoot/myFolder"); | ||
140 | |||
141 | RESULT: | ||
142 | Adds a custom root to the paths and creates a zip file with this structure: | ||
143 | |||
144 | myRoot/myFolder/dir1/ | ||
145 | myRoot/myFolder/dir1/file1.1 | ||
146 | myRoot/myFolder/dir1/file1.2 | ||
147 | myRoot/myFolder/dir1/dir1.1/ | ||
148 | myRoot/myFolder/dir1/dir1.2/file1.2.1 | ||
149 | |||
150 | EXAMPLE 3: | ||
151 | myZipInstance.addDirectory("/root/dir1", Zip::AbsolutePaths); | ||
152 | |||
153 | NOTE: | ||
154 | Same as calling addDirectory(SOME_PATH, PARENT_PATH_of_SOME_PATH). | ||
155 | |||
156 | RESULT: | ||
157 | Preserves absolute paths and creates a zip file with this structure: | ||
158 | |||
159 | /root/dir1/ | ||
160 | /root/dir1/file1.1 | ||
161 | /root/dir1/file1.2 | ||
162 | /root/dir1/dir1.1/ | ||
163 | /root/dir1/dir1.2/file1.2.1 | ||
164 | |||
165 | EXAMPLE 4: | ||
166 | myZipInstance.setPassword("hellopass"); | ||
167 | myZipInstance.addDirectory("/root/dir1", "/"); | ||
168 | |||
169 | RESULT: | ||
170 | Adds and encrypts the files in /root/dir1, creating the following zip structure: | ||
171 | |||
172 | /dir1/ | ||
173 | /dir1/file1.1 | ||
174 | /dir1/file1.2 | ||
175 | /dir1/dir1.1/ | ||
176 | /dir1/dir1.2/file1.2.1 | ||
177 | |||
178 | \endverbatim | ||
179 | */ | ||
180 | |||
181 | /*! \enum Zip::ErrorCode The result of a compression operation. | ||
182 | \value Zip::Ok No error occurred. | ||
183 | \value Zip::ZlibInit Failed to init or load the zlib library. | ||
184 | \value Zip::ZlibError The zlib library returned some error. | ||
185 | \value Zip::FileExists The file already exists and will not be overwritten. | ||
186 | \value Zip::OpenFailed Unable to create or open a device. | ||
187 | \value Zip::NoOpenArchive CreateArchive() has not been called yet. | ||
188 | \value Zip::FileNotFound File or directory does not exist. | ||
189 | \value Zip::ReadFailed Reading of a file failed. | ||
190 | \value Zip::WriteFailed Writing of a file failed. | ||
191 | \value Zip::SeekFailed Seek failed. | ||
192 | */ | ||
193 | |||
194 | /*! \enum Zip::CompressionLevel Returns the result of a decompression operation. | ||
195 | \value Zip::Store No compression. | ||
196 | \value Zip::Deflate1 Deflate compression level 1(lowest compression). | ||
197 | \value Zip::Deflate1 Deflate compression level 2. | ||
198 | \value Zip::Deflate1 Deflate compression level 3. | ||
199 | \value Zip::Deflate1 Deflate compression level 4. | ||
200 | \value Zip::Deflate1 Deflate compression level 5. | ||
201 | \value Zip::Deflate1 Deflate compression level 6. | ||
202 | \value Zip::Deflate1 Deflate compression level 7. | ||
203 | \value Zip::Deflate1 Deflate compression level 8. | ||
204 | \value Zip::Deflate1 Deflate compression level 9 (maximum compression). | ||
205 | \value Zip::AutoCPU Adapt compression level to CPU speed (faster CPU => better compression). | ||
206 | \value Zip::AutoMIME Adapt compression level to MIME type of the file being compressed. | ||
207 | \value Zip::AutoFull Use both CPU and MIME type detection. | ||
208 | */ | ||
209 | |||
210 | |||
211 | /************************************************************************ | ||
212 | Public interface | ||
213 | *************************************************************************/ | ||
214 | |||
215 | /*! | ||
216 | Creates a new Zip file compressor. | ||
217 | */ | ||
218 | Zip::Zip() | ||
219 | { | ||
220 | d = new ZipPrivate; | ||
221 | } | ||
222 | |||
223 | /*! | ||
224 | Closes any open archive and releases used resources. | ||
225 | */ | ||
226 | Zip::~Zip() | ||
227 | { | ||
228 | closeArchive(); | ||
229 | delete d; | ||
230 | } | ||
231 | |||
232 | /*! | ||
233 | Returns true if there is an open archive. | ||
234 | */ | ||
235 | bool Zip::isOpen() const | ||
236 | { | ||
237 | return d->device != 0; | ||
238 | } | ||
239 | |||
240 | /*! | ||
241 | Sets the password to be used for the next files being added! | ||
242 | Files added before calling this method will use the previously | ||
243 | set password (if any). | ||
244 | Closing the archive won't clear the password! | ||
245 | */ | ||
246 | void Zip::setPassword(const QString& pwd) | ||
247 | { | ||
248 | d->password = pwd; | ||
249 | } | ||
250 | |||
251 | //! Convenience method, clears the current password. | ||
252 | void Zip::clearPassword() | ||
253 | { | ||
254 | d->password.clear(); | ||
255 | } | ||
256 | |||
257 | //! Returns the currently used password. | ||
258 | QString Zip::password() const | ||
259 | { | ||
260 | return d->password; | ||
261 | } | ||
262 | |||
263 | /*! | ||
264 | Attempts to create a new Zip archive. If \p overwrite is true and the file | ||
265 | already exist it will be overwritten. | ||
266 | Any open archive will be closed. | ||
267 | */ | ||
268 | Zip::ErrorCode Zip::createArchive(const QString& filename, bool overwrite) | ||
269 | { | ||
270 | QFile* file = new QFile(filename); | ||
271 | |||
272 | if (file->exists() && !overwrite) { | ||
273 | delete file; | ||
274 | return Zip::FileExists; | ||
275 | } | ||
276 | |||
277 | if (!file->open(QIODevice::WriteOnly)) { | ||
278 | delete file; | ||
279 | return Zip::OpenFailed; | ||
280 | } | ||
281 | |||
282 | Zip::ErrorCode ec = createArchive(file); | ||
283 | if (ec != Zip::Ok) { | ||
284 | file->remove(); | ||
285 | } | ||
286 | |||
287 | return ec; | ||
288 | } | ||
289 | |||
290 | /*! | ||
291 | Attempts to create a new Zip archive. If there is another open archive this will be closed. | ||
292 | \warning The class takes ownership of the device! | ||
293 | */ | ||
294 | Zip::ErrorCode Zip::createArchive(QIODevice* device) | ||
295 | { | ||
296 | if (device == 0) | ||
297 | { | ||
298 | qDebug() << "Invalid device."; | ||
299 | return Zip::OpenFailed; | ||
300 | } | ||
301 | |||
302 | return d->createArchive(device); | ||
303 | } | ||
304 | |||
305 | /*! | ||
306 | Returns the current archive comment. | ||
307 | */ | ||
308 | QString Zip::archiveComment() const | ||
309 | { | ||
310 | return d->comment; | ||
311 | } | ||
312 | |||
313 | /*! | ||
314 | Sets the comment for this archive. Note: createArchive() should have been | ||
315 | called before. | ||
316 | */ | ||
317 | void Zip::setArchiveComment(const QString& comment) | ||
318 | { | ||
319 | if (d->device != 0) | ||
320 | d->comment = comment; | ||
321 | } | ||
322 | |||
323 | /*! | ||
324 | Convenience method, same as calling | ||
325 | Zip::addDirectory(const QString&,const QString&,CompressionLevel) | ||
326 | with an empty \p root parameter (or with the parent directory of \p path if the | ||
327 | AbsolutePaths options is set). | ||
328 | |||
329 | The ExtractionOptions are checked in the order they are defined in the zip.h heaser file. | ||
330 | This means that the last one overwrites the previous one (if some conflict occurs), i.e. | ||
331 | Zip::IgnorePaths | Zip::AbsolutePaths would be interpreted as Zip::IgnorePaths. | ||
332 | */ | ||
333 | Zip::ErrorCode Zip::addDirectory(const QString& path, CompressionOptions options, CompressionLevel level) | ||
334 | { | ||
335 | return addDirectory(path, QString(), options, level); | ||
336 | } | ||
337 | |||
338 | /*! | ||
339 | Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) | ||
340 | with the Zip::RelativePaths flag as compression option. | ||
341 | */ | ||
342 | Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionLevel level) | ||
343 | { | ||
344 | return addDirectory(path, root, Zip::RelativePaths, level); | ||
345 | } | ||
346 | |||
347 | /*! | ||
348 | Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) | ||
349 | with the Zip::IgnorePaths flag as compression option and an empty \p root parameter. | ||
350 | */ | ||
351 | Zip::ErrorCode Zip::addDirectoryContents(const QString& path, CompressionLevel level) | ||
352 | { | ||
353 | return addDirectory(path, QString(), IgnorePaths, level); | ||
354 | } | ||
355 | |||
356 | /*! | ||
357 | Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel) | ||
358 | with the Zip::IgnorePaths flag as compression option. | ||
359 | */ | ||
360 | Zip::ErrorCode Zip::addDirectoryContents(const QString& path, const QString& root, CompressionLevel level) | ||
361 | { | ||
362 | return addDirectory(path, root, IgnorePaths, level); | ||
363 | } | ||
364 | |||
365 | /*! | ||
366 | Recursively adds files contained in \p dir to the archive, using \p root as name for the root folder. | ||
367 | Stops adding files if some error occurs. | ||
368 | |||
369 | The ExtractionOptions are checked in the order they are defined in the zip.h heaser file. | ||
370 | This means that the last one overwrites the previous one (if some conflict occurs), i.e. | ||
371 | Zip::IgnorePaths | Zip::AbsolutePaths would be interpreted as Zip::IgnorePaths. | ||
372 | |||
373 | The \p root parameter is ignored with the Zip::IgnorePaths parameter and used as path prefix (a trailing / | ||
374 | is always added as directory separator!) otherwise (even with Zip::AbsolutePaths set!). | ||
375 | */ | ||
376 | Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionOptions options, CompressionLevel level) | ||
377 | { | ||
378 | // qDebug() << QString("addDir(path=%1, root=%2)").arg(path, root); | ||
379 | |||
380 | // Bad boy didn't call createArchive() yet :) | ||
381 | if (d->device == 0) | ||
382 | return Zip::NoOpenArchive; | ||
383 | |||
384 | QDir dir(path); | ||
385 | if (!dir.exists()) | ||
386 | return Zip::FileNotFound; | ||
387 | |||
388 | // Remove any trailing separator | ||
389 | QString actualRoot = root.trimmed(); | ||
390 | |||
391 | // Preserve Unix root | ||
392 | if (actualRoot != "/") | ||
393 | { | ||
394 | while (actualRoot.endsWith("/") || actualRoot.endsWith("\\")) | ||
395 | actualRoot.truncate(actualRoot.length() - 1); | ||
396 | } | ||
397 | |||
398 | // QDir::cleanPath() fixes some issues with QDir::dirName() | ||
399 | QFileInfo current(QDir::cleanPath(path)); | ||
400 | |||
401 | if (!actualRoot.isEmpty() && actualRoot != "/") | ||
402 | actualRoot.append("/"); | ||
403 | |||
404 | /* This part is quite confusing and needs some test or check */ | ||
405 | /* An attempt to compress the / root directory evtl. using a root prefix should be a good test */ | ||
406 | if (options.testFlag(AbsolutePaths) && !options.testFlag(IgnorePaths)) | ||
407 | { | ||
408 | QString absolutePath = d->extractRoot(path); | ||
409 | if (!absolutePath.isEmpty() && absolutePath != "/") | ||
410 | absolutePath.append("/"); | ||
411 | actualRoot.append(absolutePath); | ||
412 | } | ||
413 | |||
414 | if (!options.testFlag(IgnorePaths)) | ||
415 | { | ||
416 | actualRoot = actualRoot.append(QDir(current.absoluteFilePath()).dirName()); | ||
417 | actualRoot.append("/"); | ||
418 | } | ||
419 | |||
420 | // actualRoot now contains the path of the file relative to the zip archive | ||
421 | // with a trailing / | ||
422 | |||
423 | QFileInfoList list = dir.entryInfoList( | ||
424 | QDir::Files | | ||
425 | QDir::Dirs | | ||
426 | QDir::NoDotAndDotDot | | ||
427 | QDir::NoSymLinks); | ||
428 | |||
429 | ErrorCode ec = Zip::Ok; | ||
430 | bool filesAdded = false; | ||
431 | |||
432 | CompressionOptions recursionOptions; | ||
433 | if (options.testFlag(IgnorePaths)) | ||
434 | recursionOptions |= IgnorePaths; | ||
435 | else recursionOptions |= RelativePaths; | ||
436 | |||
437 | for (int i = 0; i < list.size() && ec == Zip::Ok; ++i) | ||
438 | { | ||
439 | QFileInfo info = list.at(i); | ||
440 | |||
441 | if (info.isDir()) | ||
442 | { | ||
443 | // Recursion :) | ||
444 | ec = addDirectory(info.absoluteFilePath(), actualRoot, recursionOptions, level); | ||
445 | } | ||
446 | else | ||
447 | { | ||
448 | ec = d->createEntry(info, actualRoot, level); | ||
449 | filesAdded = true; | ||
450 | } | ||
451 | } | ||
452 | |||
453 | |||
454 | // We need an explicit record for this dir | ||
455 | // Non-empty directories don't need it because they have a path component in the filename | ||
456 | if (!filesAdded && !options.testFlag(IgnorePaths)) | ||
457 | ec = d->createEntry(current, actualRoot, level); | ||
458 | |||
459 | return ec; | ||
460 | } | ||
461 | |||
462 | /*! | ||
463 | Closes the archive and writes any pending data. | ||
464 | */ | ||
465 | Zip::ErrorCode Zip::closeArchive() | ||
466 | { | ||
467 | Zip::ErrorCode ec = d->closeArchive(); | ||
468 | d->reset(); | ||
469 | return ec; | ||
470 | } | ||
471 | |||
472 | /*! | ||
473 | Returns a locale translated error string for a given error code. | ||
474 | */ | ||
475 | QString Zip::formatError(Zip::ErrorCode c) const | ||
476 | { | ||
477 | switch (c) | ||
478 | { | ||
479 | case Ok: return QCoreApplication::translate("Zip", "ZIP operation completed successfully."); break; | ||
480 | case ZlibInit: return QCoreApplication::translate("Zip", "Failed to initialize or load zlib library."); break; | ||
481 | case ZlibError: return QCoreApplication::translate("Zip", "zlib library error."); break; | ||
482 | case OpenFailed: return QCoreApplication::translate("Zip", "Unable to create or open file."); break; | ||
483 | case NoOpenArchive: return QCoreApplication::translate("Zip", "No archive has been created yet."); break; | ||
484 | case FileNotFound: return QCoreApplication::translate("Zip", "File or directory does not exist."); break; | ||
485 | case ReadFailed: return QCoreApplication::translate("Zip", "File read error."); break; | ||
486 | case WriteFailed: return QCoreApplication::translate("Zip", "File write error."); break; | ||
487 | case SeekFailed: return QCoreApplication::translate("Zip", "File seek error."); break; | ||
488 | default: ; | ||
489 | } | ||
490 | |||
491 | return QCoreApplication::translate("Zip", "Unknown error."); | ||
492 | } | ||
493 | |||
494 | |||
495 | /************************************************************************ | ||
496 | Private interface | ||
497 | *************************************************************************/ | ||
498 | |||
499 | //! \internal | ||
500 | ZipPrivate::ZipPrivate() | ||
501 | { | ||
502 | headers = 0; | ||
503 | device = 0; | ||
504 | |||
505 | // keep an unsigned pointer so we avoid to over bloat the code with casts | ||
506 | uBuffer = (unsigned char*) buffer1; | ||
507 | crcTable = (quint32*) get_crc_table(); | ||
508 | } | ||
509 | |||
510 | //! \internal | ||
511 | ZipPrivate::~ZipPrivate() | ||
512 | { | ||
513 | closeArchive(); | ||
514 | } | ||
515 | |||
516 | //! \internal | ||
517 | Zip::ErrorCode ZipPrivate::createArchive(QIODevice* dev) | ||
518 | { | ||
519 | Q_ASSERT(dev != 0); | ||
520 | |||
521 | if (device != 0) | ||
522 | closeArchive(); | ||
523 | |||
524 | device = dev; | ||
525 | |||
526 | if (!device->isOpen()) | ||
527 | { | ||
528 | if (!device->open(QIODevice::ReadOnly)) { | ||
529 | delete device; | ||
530 | device = 0; | ||
531 | qDebug() << "Unable to open device for writing."; | ||
532 | return Zip::OpenFailed; | ||
533 | } | ||
534 | } | ||
535 | |||
536 | headers = new QMap<QString,ZipEntryP*>; | ||
537 | return Zip::Ok; | ||
538 | } | ||
539 | |||
540 | //! \internal Writes a new entry in the zip file. | ||
541 | Zip::ErrorCode ZipPrivate::createEntry(const QFileInfo& file, const QString& root, Zip::CompressionLevel level) | ||
542 | { | ||
543 | //! \todo Automatic level detection (cpu, extension & file size) | ||
544 | |||
545 | // Directories and very small files are always stored | ||
546 | // (small files would get bigger due to the compression headers overhead) | ||
547 | |||
548 | // Need this for zlib | ||
549 | bool isPNGFile = false; | ||
550 | bool dirOnly = file.isDir(); | ||
551 | |||
552 | QString entryName = root; | ||
553 | |||
554 | // Directory entry | ||
555 | if (dirOnly) | ||
556 | level = Zip::Store; | ||
557 | else | ||
558 | { | ||
559 | entryName.append(file.fileName()); | ||
560 | |||
561 | QString ext = file.completeSuffix().toLower(); | ||
562 | isPNGFile = ext == "png"; | ||
563 | |||
564 | if (file.size() < ZIP_COMPRESSION_THRESHOLD) | ||
565 | level = Zip::Store; | ||
566 | else | ||
567 | switch (level) | ||
568 | { | ||
569 | case Zip::AutoCPU: | ||
570 | level = Zip::Deflate5; | ||
571 | break; | ||
572 | case Zip::AutoMIME: | ||
573 | level = detectCompressionByMime(ext); | ||
574 | break; | ||
575 | case Zip::AutoFull: | ||
576 | level = detectCompressionByMime(ext); | ||
577 | break; | ||
578 | default: | ||
579 | ; | ||
580 | } | ||
581 | } | ||
582 | |||
583 | // entryName contains the path as it should be written | ||
584 | // in the zip file records | ||
585 | // qDebug() << QString("addDir(file=%1, root=%2, entry=%3)").arg(file.absoluteFilePath(), root, entryName); | ||
586 | |||
587 | // create header and store it to write a central directory later | ||
588 | ZipEntryP* h = new ZipEntryP; | ||
589 | |||
590 | h->compMethod = (level == Zip::Store) ? 0 : 0x0008; | ||
591 | |||
592 | // Set encryption bit and set the data descriptor bit | ||
593 | // so we can use mod time instead of crc for password check | ||
594 | bool encrypt = !dirOnly && !password.isEmpty(); | ||
595 | if (encrypt) | ||
596 | h->gpFlag[0] |= 9; | ||
597 | |||
598 | QDateTime dt = file.lastModified(); | ||
599 | QDate d = dt.date(); | ||
600 | h->modDate[1] = ((d.year() - 1980) << 1) & 254; | ||
601 | h->modDate[1] |= ((d.month() >> 3) & 1); | ||
602 | h->modDate[0] = ((d.month() & 7) << 5) & 224; | ||
603 | h->modDate[0] |= d.day(); | ||
604 | |||
605 | QTime t = dt.time(); | ||
606 | h->modTime[1] = (t.hour() << 3) & 248; | ||
607 | h->modTime[1] |= ((t.minute() >> 3) & 7); | ||
608 | h->modTime[0] = ((t.minute() & 7) << 5) & 224; | ||
609 | h->modTime[0] |= t.second() / 2; | ||
610 | |||
611 | h->szUncomp = dirOnly ? 0 : file.size(); | ||
612 | |||
613 | // **** Write local file header **** | ||
614 | |||
615 | // signature | ||
616 | buffer1[0] = 'P'; buffer1[1] = 'K'; | ||
617 | buffer1[2] = 0x3; buffer1[3] = 0x4; | ||
618 | |||
619 | // version needed to extract | ||
620 | buffer1[ZIP_LH_OFF_VERS] = ZIP_VERSION; | ||
621 | buffer1[ZIP_LH_OFF_VERS + 1] = 0; | ||
622 | |||
623 | // general purpose flag | ||
624 | buffer1[ZIP_LH_OFF_GPFLAG] = h->gpFlag[0]; | ||
625 | buffer1[ZIP_LH_OFF_GPFLAG + 1] = h->gpFlag[1]; | ||
626 | |||
627 | // compression method | ||
628 | buffer1[ZIP_LH_OFF_CMET] = h->compMethod & 0xFF; | ||
629 | buffer1[ZIP_LH_OFF_CMET + 1] = (h->compMethod>>8) & 0xFF; | ||
630 | |||
631 | // last mod file time | ||
632 | buffer1[ZIP_LH_OFF_MODT] = h->modTime[0]; | ||
633 | buffer1[ZIP_LH_OFF_MODT + 1] = h->modTime[1]; | ||
634 | |||
635 | // last mod file date | ||
636 | buffer1[ZIP_LH_OFF_MODD] = h->modDate[0]; | ||
637 | buffer1[ZIP_LH_OFF_MODD + 1] = h->modDate[1]; | ||
638 | |||
639 | // skip crc (4bytes) [14,15,16,17] | ||
640 | |||
641 | // skip compressed size but include evtl. encryption header (4bytes: [18,19,20,21]) | ||
642 | buffer1[ZIP_LH_OFF_CSIZE] = | ||
643 | buffer1[ZIP_LH_OFF_CSIZE + 1] = | ||
644 | buffer1[ZIP_LH_OFF_CSIZE + 2] = | ||
645 | buffer1[ZIP_LH_OFF_CSIZE + 3] = 0; | ||
646 | |||
647 | h->szComp = encrypt ? ZIP_LOCAL_ENC_HEADER_SIZE : 0; | ||
648 | |||
649 | // uncompressed size [22,23,24,25] | ||
650 | setULong(h->szUncomp, buffer1, ZIP_LH_OFF_USIZE); | ||
651 | |||
652 | // filename length | ||
653 | QByteArray entryNameBytes = entryName.toAscii(); | ||
654 | int sz = entryNameBytes.size(); | ||
655 | |||
656 | buffer1[ZIP_LH_OFF_NAMELEN] = sz & 0xFF; | ||
657 | buffer1[ZIP_LH_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF; | ||
658 | |||
659 | // extra field length | ||
660 | buffer1[ZIP_LH_OFF_XLEN] = buffer1[ZIP_LH_OFF_XLEN + 1] = 0; | ||
661 | |||
662 | // Store offset to write crc and compressed size | ||
663 | h->lhOffset = device->pos(); | ||
664 | quint32 crcOffset = h->lhOffset + ZIP_LH_OFF_CRC; | ||
665 | |||
666 | if (device->write(buffer1, ZIP_LOCAL_HEADER_SIZE) != ZIP_LOCAL_HEADER_SIZE) | ||
667 | { | ||
668 | delete h; | ||
669 | return Zip::WriteFailed; | ||
670 | } | ||
671 | |||
672 | // Write out filename | ||
673 | if (device->write(entryNameBytes) != sz) | ||
674 | { | ||
675 | delete h; | ||
676 | return Zip::WriteFailed; | ||
677 | } | ||
678 | |||
679 | // Encryption keys | ||
680 | quint32 keys[3] = { 0, 0, 0 }; | ||
681 | |||
682 | if (encrypt) | ||
683 | { | ||
684 | // **** encryption header **** | ||
685 | |||
686 | // XOR with PI to ensure better random numbers | ||
687 | // with poorly implemented rand() as suggested by Info-Zip | ||
688 | srand(time(NULL) ^ 3141592654UL); | ||
689 | int randByte; | ||
690 | |||
691 | initKeys(keys); | ||
692 | for (int i=0; i<10; ++i) | ||
693 | { | ||
694 | randByte = (rand() >> 7) & 0xff; | ||
695 | buffer1[i] = decryptByte(keys[2]) ^ randByte; | ||
696 | updateKeys(keys, randByte); | ||
697 | } | ||
698 | |||
699 | // Encrypt encryption header | ||
700 | initKeys(keys); | ||
701 | for (int i=0; i<10; ++i) | ||
702 | { | ||
703 | randByte = decryptByte(keys[2]); | ||
704 | updateKeys(keys, buffer1[i]); | ||
705 | buffer1[i] ^= randByte; | ||
706 | } | ||
707 | |||
708 | // We don't know the CRC at this time, so we use the modification time | ||
709 | // as the last two bytes | ||
710 | randByte = decryptByte(keys[2]); | ||
711 | updateKeys(keys, h->modTime[0]); | ||
712 | buffer1[10] ^= randByte; | ||
713 | |||
714 | randByte = decryptByte(keys[2]); | ||
715 | updateKeys(keys, h->modTime[1]); | ||
716 | buffer1[11] ^= randByte; | ||
717 | |||
718 | // Write out encryption header | ||
719 | if (device->write(buffer1, ZIP_LOCAL_ENC_HEADER_SIZE) != ZIP_LOCAL_ENC_HEADER_SIZE) | ||
720 | { | ||
721 | delete h; | ||
722 | return Zip::WriteFailed; | ||
723 | } | ||
724 | } | ||
725 | |||
726 | qint64 written = 0; | ||
727 | quint32 crc = crc32(0L, Z_NULL, 0); | ||
728 | |||
729 | if (!dirOnly) | ||
730 | { | ||
731 | QFile actualFile(file.absoluteFilePath()); | ||
732 | if (!actualFile.open(QIODevice::ReadOnly)) | ||
733 | { | ||
734 | qDebug() << QString("An error occurred while opening %1").arg(file.absoluteFilePath()); | ||
735 | return Zip::OpenFailed; | ||
736 | } | ||
737 | |||
738 | // Write file data | ||
739 | qint64 read = 0; | ||
740 | qint64 totRead = 0; | ||
741 | qint64 toRead = actualFile.size(); | ||
742 | |||
743 | if (level == Zip::Store) | ||
744 | { | ||
745 | while ( (read = actualFile.read(buffer1, ZIP_READ_BUFFER)) > 0 ) | ||
746 | { | ||
747 | crc = crc32(crc, uBuffer, read); | ||
748 | |||
749 | if (password != 0) | ||
750 | encryptBytes(keys, buffer1, read); | ||
751 | |||
752 | if ( (written = device->write(buffer1, read)) != read ) | ||
753 | { | ||
754 | actualFile.close(); | ||
755 | delete h; | ||
756 | return Zip::WriteFailed; | ||
757 | } | ||
758 | } | ||
759 | } | ||
760 | else | ||
761 | { | ||
762 | z_stream zstr; | ||
763 | |||
764 | // Initialize zalloc, zfree and opaque before calling the init function | ||
765 | zstr.zalloc = Z_NULL; | ||
766 | zstr.zfree = Z_NULL; | ||
767 | zstr.opaque = Z_NULL; | ||
768 | |||
769 | int zret; | ||
770 | |||
771 | // Use deflateInit2 with negative windowBits to get raw compression | ||
772 | if ((zret = deflateInit2_( | ||
773 | &zstr, | ||
774 | (int)level, | ||
775 | Z_DEFLATED, | ||
776 | -MAX_WBITS, | ||
777 | 8, | ||
778 | isPNGFile ? Z_RLE : Z_DEFAULT_STRATEGY, | ||
779 | ZLIB_VERSION, | ||
780 | sizeof(z_stream) | ||
781 | )) != Z_OK ) | ||
782 | { | ||
783 | actualFile.close(); | ||
784 | qDebug() << "Could not initialize zlib for compression"; | ||
785 | delete h; | ||
786 | return Zip::ZlibError; | ||
787 | } | ||
788 | |||
789 | qint64 compressed; | ||
790 | |||
791 | int flush = Z_NO_FLUSH; | ||
792 | |||
793 | do | ||
794 | { | ||
795 | read = actualFile.read(buffer1, ZIP_READ_BUFFER); | ||
796 | totRead += read; | ||
797 | |||
798 | if (read == 0) | ||
799 | break; | ||
800 | if (read < 0) | ||
801 | { | ||
802 | actualFile.close(); | ||
803 | deflateEnd(&zstr); | ||
804 | qDebug() << QString("Error while reading %1").arg(file.absoluteFilePath()); | ||
805 | delete h; | ||
806 | return Zip::ReadFailed; | ||
807 | } | ||
808 | |||
809 | crc = crc32(crc, uBuffer, read); | ||
810 | |||
811 | zstr.next_in = (Bytef*) buffer1; | ||
812 | zstr.avail_in = (uInt)read; | ||
813 | |||
814 | // Tell zlib if this is the last chunk we want to encode | ||
815 | // by setting the flush parameter to Z_FINISH | ||
816 | flush = (totRead == toRead) ? Z_FINISH : Z_NO_FLUSH; | ||
817 | |||
818 | // Run deflate() on input until output buffer not full | ||
819 | // finish compression if all of source has been read in | ||
820 | do | ||
821 | { | ||
822 | zstr.next_out = (Bytef*) buffer2; | ||
823 | zstr.avail_out = ZIP_READ_BUFFER; | ||
824 | |||
825 | zret = deflate(&zstr, flush); | ||
826 | // State not clobbered | ||
827 | Q_ASSERT(zret != Z_STREAM_ERROR); | ||
828 | |||
829 | // Write compressed data to file and empty buffer | ||
830 | compressed = ZIP_READ_BUFFER - zstr.avail_out; | ||
831 | |||
832 | if (password != 0) | ||
833 | encryptBytes(keys, buffer2, compressed); | ||
834 | |||
835 | if (device->write(buffer2, compressed) != compressed) | ||
836 | { | ||
837 | deflateEnd(&zstr); | ||
838 | actualFile.close(); | ||
839 | qDebug() << QString("Error while writing %1").arg(file.absoluteFilePath()); | ||
840 | delete h; | ||
841 | return Zip::WriteFailed; | ||
842 | } | ||
843 | |||
844 | written += compressed; | ||
845 | |||
846 | } while (zstr.avail_out == 0); | ||
847 | |||
848 | // All input will be used | ||
849 | Q_ASSERT(zstr.avail_in == 0); | ||
850 | |||
851 | } while (flush != Z_FINISH); | ||
852 | |||
853 | // Stream will be complete | ||
854 | Q_ASSERT(zret == Z_STREAM_END); | ||
855 | |||
856 | deflateEnd(&zstr); | ||
857 | |||
858 | } // if (level != STORE) | ||
859 | |||
860 | actualFile.close(); | ||
861 | } | ||
862 | |||
863 | // Store end of entry offset | ||
864 | quint32 current = device->pos(); | ||
865 | |||
866 | // Update crc and compressed size in local header | ||
867 | if (!device->seek(crcOffset)) | ||
868 | { | ||
869 | delete h; | ||
870 | return Zip::SeekFailed; | ||
871 | } | ||
872 | |||
873 | h->crc = dirOnly ? 0 : crc; | ||
874 | h->szComp += written; | ||
875 | |||
876 | setULong(h->crc, buffer1, 0); | ||
877 | setULong(h->szComp, buffer1, 4); | ||
878 | if ( device->write(buffer1, 8) != 8) | ||
879 | { | ||
880 | delete h; | ||
881 | return Zip::WriteFailed; | ||
882 | } | ||
883 | |||
884 | // Seek to end of entry | ||
885 | if (!device->seek(current)) | ||
886 | { | ||
887 | delete h; | ||
888 | return Zip::SeekFailed; | ||
889 | } | ||
890 | |||
891 | if ((h->gpFlag[0] & 8) == 8) | ||
892 | { | ||
893 | // Write data descriptor | ||
894 | |||
895 | // Signature: PK\7\8 | ||
896 | buffer1[0] = 'P'; | ||
897 | buffer1[1] = 'K'; | ||
898 | buffer1[2] = 0x07; | ||
899 | buffer1[3] = 0x08; | ||
900 | |||
901 | // CRC | ||
902 | setULong(h->crc, buffer1, ZIP_DD_OFF_CRC32); | ||
903 | |||
904 | // Compressed size | ||
905 | setULong(h->szComp, buffer1, ZIP_DD_OFF_CSIZE); | ||
906 | |||
907 | // Uncompressed size | ||
908 | setULong(h->szUncomp, buffer1, ZIP_DD_OFF_USIZE); | ||
909 | |||
910 | if (device->write(buffer1, ZIP_DD_SIZE_WS) != ZIP_DD_SIZE_WS) | ||
911 | { | ||
912 | delete h; | ||
913 | return Zip::WriteFailed; | ||
914 | } | ||
915 | } | ||
916 | |||
917 | headers->insert(entryName, h); | ||
918 | return Zip::Ok; | ||
919 | } | ||
920 | |||
921 | //! \internal | ||
922 | int ZipPrivate::decryptByte(quint32 key2) const | ||
923 | { | ||
924 | quint16 temp = ((quint16)(key2) & 0xffff) | 2; | ||
925 | return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); | ||
926 | } | ||
927 | |||
928 | //! \internal Writes an quint32 (4 bytes) to a byte array at given offset. | ||
929 | void ZipPrivate::setULong(quint32 v, char* buffer, unsigned int offset) | ||
930 | { | ||
931 | buffer[offset+3] = ((v >> 24) & 0xFF); | ||
932 | buffer[offset+2] = ((v >> 16) & 0xFF); | ||
933 | buffer[offset+1] = ((v >> 8) & 0xFF); | ||
934 | buffer[offset] = (v & 0xFF); | ||
935 | } | ||
936 | |||
937 | //! \internal Initializes decryption keys using a password. | ||
938 | void ZipPrivate::initKeys(quint32* keys) const | ||
939 | { | ||
940 | // Encryption keys initialization constants are taken from the | ||
941 | // PKZip file format specification docs | ||
942 | keys[0] = 305419896L; | ||
943 | keys[1] = 591751049L; | ||
944 | keys[2] = 878082192L; | ||
945 | |||
946 | QByteArray pwdBytes = password.toAscii(); | ||
947 | int sz = pwdBytes.size(); | ||
948 | const char* ascii = pwdBytes.data(); | ||
949 | |||
950 | for (int i=0; i<sz; ++i) | ||
951 | updateKeys(keys, (int)ascii[i]); | ||
952 | } | ||
953 | |||
954 | //! \internal Updates encryption keys. | ||
955 | void ZipPrivate::updateKeys(quint32* keys, int c) const | ||
956 | { | ||
957 | keys[0] = CRC32(keys[0], c); | ||
958 | keys[1] += keys[0] & 0xff; | ||
959 | keys[1] = keys[1] * 134775813L + 1; | ||
960 | keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24); | ||
961 | } | ||
962 | |||
963 | //! \internal Encrypts a byte array. | ||
964 | void ZipPrivate::encryptBytes(quint32* keys, char* buffer, qint64 read) | ||
965 | { | ||
966 | char t; | ||
967 | |||
968 | for (int i=0; i<(int)read; ++i) | ||
969 | { | ||
970 | t = buffer[i]; | ||
971 | buffer[i] ^= decryptByte(keys[2]); | ||
972 | updateKeys(keys, t); | ||
973 | } | ||
974 | } | ||
975 | |||
976 | //! \internal Detects the best compression level for a given file extension. | ||
977 | Zip::CompressionLevel ZipPrivate::detectCompressionByMime(const QString& ext) | ||
978 | { | ||
979 | // files really hard to compress | ||
980 | if ((ext == "png") || | ||
981 | (ext == "jpg") || | ||
982 | (ext == "jpeg") || | ||
983 | (ext == "mp3") || | ||
984 | (ext == "ogg") || | ||
985 | (ext == "ogm") || | ||
986 | (ext == "avi") || | ||
987 | (ext == "mov") || | ||
988 | (ext == "rm") || | ||
989 | (ext == "ra") || | ||
990 | (ext == "zip") || | ||
991 | (ext == "rar") || | ||
992 | (ext == "bz2") || | ||
993 | (ext == "gz") || | ||
994 | (ext == "7z") || | ||
995 | (ext == "z") || | ||
996 | (ext == "jar") | ||
997 | ) return Zip::Store; | ||
998 | |||
999 | // files slow and hard to compress | ||
1000 | if ((ext == "exe") || | ||
1001 | (ext == "bin") || | ||
1002 | (ext == "rpm") || | ||
1003 | (ext == "deb") | ||
1004 | ) return Zip::Deflate2; | ||
1005 | |||
1006 | return Zip::Deflate9; | ||
1007 | } | ||
1008 | |||
1009 | /*! | ||
1010 | Closes the current archive and writes out pending data. | ||
1011 | */ | ||
1012 | Zip::ErrorCode ZipPrivate::closeArchive() | ||
1013 | { | ||
1014 | // Close current archive by writing out central directory | ||
1015 | // and free up resources | ||
1016 | |||
1017 | if (device == 0) | ||
1018 | return Zip::Ok; | ||
1019 | |||
1020 | if (headers == 0) | ||
1021 | return Zip::Ok; | ||
1022 | |||
1023 | const ZipEntryP* h; | ||
1024 | |||
1025 | unsigned int sz; | ||
1026 | quint32 szCentralDir = 0; | ||
1027 | quint32 offCentralDir = device->pos(); | ||
1028 | |||
1029 | for (QMap<QString,ZipEntryP*>::ConstIterator itr = headers->constBegin(); itr != headers->constEnd(); ++itr) | ||
1030 | { | ||
1031 | h = itr.value(); | ||
1032 | |||
1033 | // signature | ||
1034 | buffer1[0] = 'P'; | ||
1035 | buffer1[1] = 'K'; | ||
1036 | buffer1[2] = 0x01; | ||
1037 | buffer1[3] = 0x02; | ||
1038 | |||
1039 | // version made by (currently only MS-DOS/FAT - no symlinks or other stuff supported) | ||
1040 | buffer1[ZIP_CD_OFF_MADEBY] = buffer1[ZIP_CD_OFF_MADEBY + 1] = 0; | ||
1041 | |||
1042 | // version needed to extract | ||
1043 | buffer1[ZIP_CD_OFF_VERSION] = ZIP_VERSION; | ||
1044 | buffer1[ZIP_CD_OFF_VERSION + 1] = 0; | ||
1045 | |||
1046 | // general purpose flag | ||
1047 | buffer1[ZIP_CD_OFF_GPFLAG] = h->gpFlag[0]; | ||
1048 | buffer1[ZIP_CD_OFF_GPFLAG + 1] = h->gpFlag[1]; | ||
1049 | |||
1050 | // compression method | ||
1051 | buffer1[ZIP_CD_OFF_CMET] = h->compMethod & 0xFF; | ||
1052 | buffer1[ZIP_CD_OFF_CMET + 1] = (h->compMethod >> 8) & 0xFF; | ||
1053 | |||
1054 | // last mod file time | ||
1055 | buffer1[ZIP_CD_OFF_MODT] = h->modTime[0]; | ||
1056 | buffer1[ZIP_CD_OFF_MODT + 1] = h->modTime[1]; | ||
1057 | |||
1058 | // last mod file date | ||
1059 | buffer1[ZIP_CD_OFF_MODD] = h->modDate[0]; | ||
1060 | buffer1[ZIP_CD_OFF_MODD + 1] = h->modDate[1]; | ||
1061 | |||
1062 | // crc (4bytes) [16,17,18,19] | ||
1063 | setULong(h->crc, buffer1, ZIP_CD_OFF_CRC); | ||
1064 | |||
1065 | // compressed size (4bytes: [20,21,22,23]) | ||
1066 | setULong(h->szComp, buffer1, ZIP_CD_OFF_CSIZE); | ||
1067 | |||
1068 | // uncompressed size [24,25,26,27] | ||
1069 | setULong(h->szUncomp, buffer1, ZIP_CD_OFF_USIZE); | ||
1070 | |||
1071 | // filename | ||
1072 | QByteArray fileNameBytes = itr.key().toAscii(); | ||
1073 | sz = fileNameBytes.size(); | ||
1074 | buffer1[ZIP_CD_OFF_NAMELEN] = sz & 0xFF; | ||
1075 | buffer1[ZIP_CD_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF; | ||
1076 | |||
1077 | // extra field length | ||
1078 | buffer1[ZIP_CD_OFF_XLEN] = buffer1[ZIP_CD_OFF_XLEN + 1] = 0; | ||
1079 | |||
1080 | // file comment length | ||
1081 | buffer1[ZIP_CD_OFF_COMMLEN] = buffer1[ZIP_CD_OFF_COMMLEN + 1] = 0; | ||
1082 | |||
1083 | // disk number start | ||
1084 | buffer1[ZIP_CD_OFF_DISKSTART] = buffer1[ZIP_CD_OFF_DISKSTART + 1] = 0; | ||
1085 | |||
1086 | // internal file attributes | ||
1087 | buffer1[ZIP_CD_OFF_IATTR] = buffer1[ZIP_CD_OFF_IATTR + 1] = 0; | ||
1088 | |||
1089 | // external file attributes | ||
1090 | buffer1[ZIP_CD_OFF_EATTR] = | ||
1091 | buffer1[ZIP_CD_OFF_EATTR + 1] = | ||
1092 | buffer1[ZIP_CD_OFF_EATTR + 2] = | ||
1093 | buffer1[ZIP_CD_OFF_EATTR + 3] = 0; | ||
1094 | |||
1095 | // relative offset of local header [42->45] | ||
1096 | setULong(h->lhOffset, buffer1, ZIP_CD_OFF_LHOFF); | ||
1097 | |||
1098 | if (device->write(buffer1, ZIP_CD_SIZE) != ZIP_CD_SIZE) | ||
1099 | { | ||
1100 | //! \todo See if we can detect QFile objects using the Qt Meta Object System | ||
1101 | /* | ||
1102 | if (!device->remove()) | ||
1103 | qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); | ||
1104 | */ | ||
1105 | return Zip::WriteFailed; | ||
1106 | } | ||
1107 | |||
1108 | // Write out filename | ||
1109 | if ((unsigned int)device->write(fileNameBytes) != sz) | ||
1110 | { | ||
1111 | //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System | ||
1112 | /* | ||
1113 | if (!device->remove()) | ||
1114 | qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); | ||
1115 | */ | ||
1116 | return Zip::WriteFailed; | ||
1117 | } | ||
1118 | |||
1119 | szCentralDir += (ZIP_CD_SIZE + sz); | ||
1120 | |||
1121 | } // central dir headers loop | ||
1122 | |||
1123 | |||
1124 | // Write end of central directory | ||
1125 | |||
1126 | // signature | ||
1127 | buffer1[0] = 'P'; | ||
1128 | buffer1[1] = 'K'; | ||
1129 | buffer1[2] = 0x05; | ||
1130 | buffer1[3] = 0x06; | ||
1131 | |||
1132 | // number of this disk | ||
1133 | buffer1[ZIP_EOCD_OFF_DISKNUM] = buffer1[ZIP_EOCD_OFF_DISKNUM + 1] = 0; | ||
1134 | |||
1135 | // number of disk with central directory | ||
1136 | buffer1[ZIP_EOCD_OFF_CDDISKNUM] = buffer1[ZIP_EOCD_OFF_CDDISKNUM + 1] = 0; | ||
1137 | |||
1138 | // number of entries in this disk | ||
1139 | sz = headers->count(); | ||
1140 | buffer1[ZIP_EOCD_OFF_ENTRIES] = sz & 0xFF; | ||
1141 | buffer1[ZIP_EOCD_OFF_ENTRIES + 1] = (sz >> 8) & 0xFF; | ||
1142 | |||
1143 | // total number of entries | ||
1144 | buffer1[ZIP_EOCD_OFF_CDENTRIES] = buffer1[ZIP_EOCD_OFF_ENTRIES]; | ||
1145 | buffer1[ZIP_EOCD_OFF_CDENTRIES + 1] = buffer1[ZIP_EOCD_OFF_ENTRIES + 1]; | ||
1146 | |||
1147 | // size of central directory [12->15] | ||
1148 | setULong(szCentralDir, buffer1, ZIP_EOCD_OFF_CDSIZE); | ||
1149 | |||
1150 | // central dir offset [16->19] | ||
1151 | setULong(offCentralDir, buffer1, ZIP_EOCD_OFF_CDOFF); | ||
1152 | |||
1153 | // ZIP file comment length | ||
1154 | QByteArray commentBytes = comment.toAscii(); | ||
1155 | quint16 commentLength = commentBytes.size(); | ||
1156 | |||
1157 | if (commentLength == 0) | ||
1158 | { | ||
1159 | buffer1[ZIP_EOCD_OFF_COMMLEN] = buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = 0; | ||
1160 | } | ||
1161 | else | ||
1162 | { | ||
1163 | buffer1[ZIP_EOCD_OFF_COMMLEN] = commentLength & 0xFF; | ||
1164 | buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = (commentLength >> 8) & 0xFF; | ||
1165 | } | ||
1166 | |||
1167 | if (device->write(buffer1, ZIP_EOCD_SIZE) != ZIP_EOCD_SIZE) | ||
1168 | { | ||
1169 | //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System | ||
1170 | /* | ||
1171 | if (!device->remove()) | ||
1172 | qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); | ||
1173 | */ | ||
1174 | return Zip::WriteFailed; | ||
1175 | } | ||
1176 | |||
1177 | if (commentLength != 0) | ||
1178 | { | ||
1179 | if ((unsigned int)device->write(commentBytes) != commentLength) | ||
1180 | { | ||
1181 | //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System | ||
1182 | /* | ||
1183 | if (!device->remove()) | ||
1184 | qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName()); | ||
1185 | */ | ||
1186 | return Zip::WriteFailed; | ||
1187 | } | ||
1188 | } | ||
1189 | |||
1190 | return Zip::Ok; | ||
1191 | } | ||
1192 | |||
1193 | //! \internal | ||
1194 | void ZipPrivate::reset() | ||
1195 | { | ||
1196 | comment.clear(); | ||
1197 | |||
1198 | if (headers != 0) | ||
1199 | { | ||
1200 | qDeleteAll(*headers); | ||
1201 | delete headers; | ||
1202 | headers = 0; | ||
1203 | } | ||
1204 | |||
1205 | delete device; device = 0; | ||
1206 | } | ||
1207 | |||
1208 | //! \internal Returns the path of the parent directory | ||
1209 | QString ZipPrivate::extractRoot(const QString& p) | ||
1210 | { | ||
1211 | QDir d(QDir::cleanPath(p)); | ||
1212 | if (!d.exists()) | ||
1213 | return QString(); | ||
1214 | |||
1215 | if (!d.cdUp()) | ||
1216 | return QString(); | ||
1217 | |||
1218 | return d.absolutePath(); | ||
1219 | } | ||