zip.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. // Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
  2. //
  3. // This file is part of libzipper.
  4. //
  5. // libzipper is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // libzipper is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with libzipper. If not, see <http://www.gnu.org/licenses/>.
  17. #include "zipper.hh"
  18. #include "zip.hh"
  19. #include "util.hh"
  20. #include "deflate.hh"
  21. #include <algorithm>
  22. #include <cassert>
  23. #include <iostream>
  24. #include <time.h>
  25. #include <string.h>
  26. using namespace zipper;
  27. namespace
  28. {
  29. time_t convertDosDateTime(uint16_t date, uint16_t time)
  30. {
  31. struct tm parts;
  32. memset(&parts, 0, sizeof(parts));
  33. parts.tm_sec = time & 0x1F;
  34. parts.tm_min = (time & 0x7E0) >> 5;
  35. parts.tm_hour = (time >> 11);
  36. parts.tm_mday = date & 0x1F;
  37. parts.tm_mon = ((date & 0x1E0) >> 5) - 1;
  38. parts.tm_year = (date >> 9) + 80;
  39. return mktime(&parts);
  40. }
  41. void convertDosDateTime(time_t in, uint16_t& date, uint16_t& time)
  42. {
  43. struct tm buf;
  44. struct tm* parts(localtime_r(&in, &buf));
  45. time =
  46. parts->tm_sec +
  47. (parts->tm_min << 5) +
  48. (parts->tm_hour << 11);
  49. date =
  50. parts->tm_mday +
  51. ((parts->tm_mon + 1) << 5) +
  52. ((parts->tm_year - 80) << 9);
  53. }
  54. class FileEntry : public CompressedFile
  55. {
  56. public:
  57. FileEntry(
  58. const ReaderPtr& reader,
  59. uint16_t versionNeeded,
  60. uint16_t gpFlag,
  61. uint16_t compressionMethod,
  62. uint32_t crc,
  63. zsize_t compressedSize,
  64. zsize_t uncompressedSize,
  65. time_t modTime,
  66. zsize_t localHeaderOffset,
  67. std::string fileName
  68. ) :
  69. m_reader(reader),
  70. m_versionNeeded(versionNeeded),
  71. m_gpFlag(gpFlag),
  72. m_compressionMethod(compressionMethod),
  73. m_crc(crc),
  74. m_compressedSize(compressedSize),
  75. m_uncompressedSize(uncompressedSize),
  76. m_localHeaderOffset(localHeaderOffset),
  77. m_fileName(fileName)
  78. {
  79. m_modTime.tv_sec = modTime;
  80. m_modTime.tv_usec = 0;
  81. }
  82. virtual bool isDecompressSupported() const
  83. {
  84. return ((m_versionNeeded & 0xf) <= 20) &&
  85. ((m_gpFlag & 0x1) == 0) && // Not encrypted
  86. ((m_compressionMethod == 0) || (m_compressionMethod == 8));
  87. }
  88. virtual const std::string& getPath() const
  89. {
  90. return m_fileName;
  91. }
  92. virtual zsize_t getCompressedSize() const { return m_compressedSize; }
  93. virtual zsize_t getUncompressedSize() const
  94. {
  95. return m_uncompressedSize;
  96. }
  97. virtual const timeval& getModificationTime() const { return m_modTime; }
  98. virtual void decompress(Writer& writer)
  99. {
  100. enum
  101. {
  102. Signature = 0x04034b50,
  103. MinRecordBytes = 30,
  104. ChunkSize = 64*1024
  105. };
  106. std::vector<uint8_t> localRecord(MinRecordBytes);
  107. m_reader->readData(
  108. m_localHeaderOffset, MinRecordBytes, &localRecord[0]
  109. );
  110. if (read32_le(localRecord, 0) != Signature)
  111. {
  112. throw FormatException("Invalid local ZIP record");
  113. }
  114. // Don't trust the lengths for filename and extra content read from
  115. // the central records. At least for extra, these DO differ for
  116. // unknown reasons
  117. zsize_t filenameLength(read16_le(localRecord, 26));
  118. zsize_t extraLength(read16_le(localRecord, 28));
  119. zsize_t startCompressedBytes(
  120. m_localHeaderOffset +
  121. MinRecordBytes +
  122. filenameLength +
  123. extraLength
  124. );
  125. zsize_t endCompressedBytes(
  126. startCompressedBytes + m_compressedSize
  127. );
  128. if (endCompressedBytes > m_reader->getSize())
  129. {
  130. throw FormatException("Compressed file size is too long");
  131. }
  132. switch (m_compressionMethod)
  133. {
  134. case 0: // No compression
  135. {
  136. for (zsize_t pos(startCompressedBytes);
  137. pos < endCompressedBytes;
  138. pos += ChunkSize
  139. )
  140. {
  141. uint8_t buf[ChunkSize];
  142. zsize_t bytes(
  143. std::min(zsize_t(ChunkSize), endCompressedBytes - pos)
  144. );
  145. m_reader->readData(pos, bytes, &buf[0]);
  146. writer.writeData(pos, bytes, &buf[0]);
  147. }
  148. }; break;
  149. case 8: // Deflate
  150. {
  151. uint32_t crc(0);
  152. zsize_t inPos(startCompressedBytes);
  153. zsize_t outPos(0);
  154. inflate(
  155. m_reader,
  156. writer,
  157. inPos,
  158. endCompressedBytes,
  159. outPos,
  160. crc);
  161. if (m_gpFlag & 0x4) // CRC is after compressed data
  162. {
  163. uint8_t dataDescriptor[12];
  164. m_reader->readData(
  165. inPos, sizeof(dataDescriptor), &dataDescriptor[0]);
  166. m_crc = read32_le(dataDescriptor, 0);
  167. m_compressedSize = read32_le(dataDescriptor, 4);
  168. m_uncompressedSize = read32_le(dataDescriptor, 8);
  169. }
  170. if (crc != m_crc)
  171. {
  172. throw FormatException("Corrupt Data (CRC failure)");
  173. }
  174. }; break;
  175. default:
  176. throw UnsupportedException("Unsupported compression scheme");
  177. };
  178. }
  179. private:
  180. ReaderPtr m_reader;
  181. uint16_t m_versionNeeded;
  182. uint16_t m_gpFlag;
  183. uint16_t m_compressionMethod;
  184. uint32_t m_crc;
  185. zsize_t m_compressedSize;
  186. zsize_t m_uncompressedSize;
  187. timeval m_modTime;
  188. zsize_t m_localHeaderOffset;
  189. std::string m_fileName;
  190. };
  191. bool readEndCentralDirectory(
  192. const ReaderPtr& reader,
  193. zsize_t& centralDirectoryBytes,
  194. zsize_t& centralDirectoryOffset,
  195. zsize_t& centralDirectoryEntries
  196. )
  197. {
  198. // Read the end of central directory record. This
  199. // record enables us to find the remainding
  200. // records without searching for record signatures.
  201. // TODO does not consider the Zip64 entries.
  202. enum
  203. {
  204. MinRecordBytes = 22, // Minimum size with no comment
  205. MaxCommentBytes = 65535, // 2 bytes to store comment length
  206. Signature = 0x06054b50
  207. };
  208. zsize_t providerSize(reader->getSize());
  209. if (providerSize < MinRecordBytes)
  210. {
  211. throw FormatException("Too small");
  212. }
  213. size_t bufSize(
  214. std::min(zsize_t(MinRecordBytes + MaxCommentBytes), providerSize)
  215. );
  216. std::vector<uint8_t> buffer(bufSize);
  217. reader->readData(providerSize - bufSize, bufSize, &buffer[0]);
  218. // Need to search for this record, as it ends in a variable-length
  219. // comment field. Search backwards, with the assumption that the
  220. // comment doesn't exist, or is much smaller than the maximum
  221. // length
  222. bool recordFound(false);
  223. ssize_t pos(bufSize - MinRecordBytes);
  224. for (; pos >= 0; --pos)
  225. {
  226. recordFound = (read32_le(buffer, pos) == Signature);
  227. break;
  228. }
  229. if (recordFound)
  230. {
  231. if (read16_le(buffer, pos + 4) != 0)
  232. {
  233. throw UnsupportedException("Spanned disks not supported");
  234. }
  235. centralDirectoryBytes = read32_le(buffer, pos + 12);
  236. centralDirectoryOffset = read32_le(buffer, pos + 16);
  237. centralDirectoryEntries = read16_le(buffer, pos + 10);
  238. }
  239. return recordFound;
  240. }
  241. std::vector<CompressedFilePtr>
  242. readCentralDirectory(const ReaderPtr& reader)
  243. {
  244. enum Constants
  245. {
  246. MinRecordBytes = 46,
  247. Signature = 0x02014b50
  248. };
  249. zsize_t centralDirectoryBytes(0);
  250. zsize_t centralDirectoryOffset(0);
  251. zsize_t centralDirectoryEntries(0);
  252. bool isZip(
  253. readEndCentralDirectory(
  254. reader,
  255. centralDirectoryBytes,
  256. centralDirectoryOffset,
  257. centralDirectoryEntries
  258. )
  259. );
  260. (void) isZip; // Avoid unused warning.
  261. assert(isZip);
  262. std::vector<uint8_t> buffer(centralDirectoryBytes);
  263. reader->readData(
  264. centralDirectoryOffset,
  265. centralDirectoryBytes,
  266. &buffer[0]
  267. );
  268. zsize_t pos(0);
  269. std::vector<CompressedFilePtr> entries;
  270. while ((pos + MinRecordBytes) < buffer.size())
  271. {
  272. if (read32_le(buffer, pos) != Signature)
  273. {
  274. // Unknown record type.
  275. pos += 1;
  276. continue;
  277. }
  278. uint16_t versionNeeded(read16_le(buffer, pos + 6));
  279. uint16_t gpFlag(read16_le(buffer, pos + 8));
  280. uint16_t compressionMethod(read16_le(buffer, pos + 10));
  281. uint16_t modTime(read16_le(buffer, pos + 12));
  282. uint16_t modDate(read16_le(buffer, pos + 14));
  283. uint32_t crc(read32_le(buffer, pos + 16));
  284. uint32_t compressedSize(read32_le(buffer, pos + 20));
  285. uint32_t uncompressedSize(read32_le(buffer, pos + 24));
  286. size_t fileNameLen(read16_le(buffer, pos + 28));
  287. size_t extraLen(read16_le(buffer, pos + 30));
  288. size_t commentLen(read16_le(buffer, pos + 32));
  289. uint32_t localHeaderOffset(read32_le(buffer, pos + 42));
  290. if ((fileNameLen + extraLen + commentLen + MinRecordBytes + pos) >
  291. buffer.size()
  292. )
  293. {
  294. throw FormatException("File comments are too long");
  295. }
  296. std::string fileName(
  297. &buffer[pos + MinRecordBytes],
  298. &buffer[pos + MinRecordBytes + fileNameLen]
  299. );
  300. entries.push_back(
  301. CompressedFilePtr(
  302. new FileEntry(
  303. reader,
  304. versionNeeded,
  305. gpFlag,
  306. compressionMethod,
  307. crc,
  308. compressedSize,
  309. uncompressedSize,
  310. convertDosDateTime(modDate, modTime),
  311. localHeaderOffset,
  312. fileName
  313. )
  314. )
  315. );
  316. pos += MinRecordBytes + fileNameLen + extraLen + commentLen;
  317. }
  318. return entries;
  319. }
  320. }
  321. void
  322. zipper::zip(
  323. const std::string& filename,
  324. const Reader& reader,
  325. const WriterPtr& writer,
  326. ZipFileRecord& outRecord)
  327. {
  328. enum Constants
  329. {
  330. ChunkSize = 64*1024,
  331. WindowBits = 15,
  332. TimePos = 10
  333. };
  334. static uint8_t Header[] =
  335. {
  336. 0x50, 0x4b, 0x03, 0x04, // Header
  337. 20, // Version (2.0)
  338. 0, // File attributes
  339. 0,0, // gp flag.
  340. 8,0, // deflate method
  341. 0,0, // file time
  342. 0,0, // file date
  343. 0,0,0,0, // CRC32
  344. 0,0,0,0, // Compressed size
  345. 0,0,0,0 // Uncompressed size
  346. };
  347. zsize_t outPos(writer->getSize());
  348. outRecord.localHeaderOffset = outPos;
  349. outRecord.filename = filename;
  350. // Write header
  351. {
  352. uint8_t buffer[ChunkSize];
  353. memcpy(buffer, Header, sizeof(Header));
  354. zsize_t pos(sizeof(Header));
  355. std::string::size_type filenameSize(filename.size());
  356. if (filenameSize > (ChunkSize - pos))
  357. {
  358. filenameSize = ChunkSize - pos;
  359. }
  360. buffer[pos++] = filenameSize & 0xff;
  361. buffer[pos++] = (filenameSize >> 8);
  362. buffer[pos++] = 0; // extra field len
  363. buffer[pos++] = 0; // extra field len
  364. memcpy(buffer + pos, filename.data(), filenameSize);
  365. pos += filenameSize;
  366. writer->writeData(outPos, pos, &buffer[0]);
  367. outPos += pos;
  368. }
  369. // Write compressed data
  370. deflate(
  371. reader,
  372. writer,
  373. outPos,
  374. outRecord.uncompressedSize,
  375. outRecord.compressedSize,
  376. outRecord.crc32);
  377. // Go back and complete the header.
  378. convertDosDateTime(
  379. reader.getModTime().tv_sec, outRecord.dosDate, outRecord.dosTime);
  380. uint8_t trailer[16];
  381. write16_le(outRecord.dosTime, &trailer[0]);
  382. write16_le(outRecord.dosDate, &trailer[2]);
  383. write32_le(outRecord.crc32, &trailer[4]);
  384. write32_le(outRecord.compressedSize, &trailer[8]);
  385. write32_le(outRecord.uncompressedSize, &trailer[12]);
  386. writer->writeData(
  387. outRecord.localHeaderOffset + TimePos, sizeof(trailer), &trailer[0]);
  388. }
  389. void
  390. zipper::zipFinalise(
  391. const std::vector<ZipFileRecord>& records,
  392. const WriterPtr& writer)
  393. {
  394. enum Constants
  395. {
  396. ChunkSize = 64*1024
  397. };
  398. static uint8_t FileHeader[] =
  399. {
  400. 0x50, 0x4b, 0x01, 0x02, // Header
  401. 20, 0x00, // Version (2.0)
  402. 20, 0x00, // Version Needed to extract (2.0)
  403. 0,0, // gp flag.
  404. 8,0 // deflate method
  405. };
  406. zsize_t outPos(writer->getSize());
  407. uint32_t centralDirOffset(outPos);
  408. for (size_t i = 0; i < records.size(); ++i)
  409. {
  410. uint8_t buffer[ChunkSize];
  411. memcpy(buffer, FileHeader, sizeof(FileHeader));
  412. zsize_t pos(sizeof(FileHeader));
  413. write16_le(records[i].dosTime, &buffer[pos]);
  414. pos += 2;
  415. write16_le(records[i].dosDate, &buffer[pos]);
  416. pos += 2;
  417. write32_le(records[i].crc32, &buffer[pos]);
  418. pos += 4;
  419. write32_le(records[i].compressedSize, &buffer[pos]);
  420. pos += 4;
  421. write32_le(records[i].uncompressedSize, &buffer[pos]);
  422. pos += 4;
  423. std::string::size_type filenameSize(records[i].filename.size());
  424. if (filenameSize > (ChunkSize - pos))
  425. {
  426. filenameSize = ChunkSize - pos;
  427. }
  428. write16_le(filenameSize, &buffer[pos]);
  429. pos += 2;
  430. write16_le(0, &buffer[pos]); // extra field len
  431. pos += 2;
  432. write16_le(0, &buffer[pos]); // file comment len
  433. pos += 2;
  434. write16_le(0, &buffer[pos]); // disk number
  435. pos += 2;
  436. write16_le(0, &buffer[pos]); // internal file attributes
  437. pos += 2;
  438. write32_le(0, &buffer[pos]); // external file attributes
  439. pos += 4;
  440. write32_le(records[i].localHeaderOffset, &buffer[pos]);
  441. pos += 4;
  442. memcpy(buffer + pos, records[i].filename.data(), filenameSize);
  443. pos += filenameSize;
  444. writer->writeData(outPos, pos, &buffer[0]);
  445. outPos += pos;
  446. }
  447. uint32_t centralDirSize(writer->getSize() - centralDirOffset);
  448. {
  449. // End-of-directory record.
  450. static uint8_t EndDirectory[] =
  451. {
  452. 0x50, 0x4b, 0x05, 0x06, // Header
  453. 0x00, 0x00, // Disk num
  454. 0x00, 0x00 // Disk with central dir
  455. };
  456. uint8_t buffer[ChunkSize];
  457. memcpy(buffer, EndDirectory, sizeof(EndDirectory));
  458. zsize_t pos(sizeof(EndDirectory));
  459. write16_le(records.size(), &buffer[pos]); // Entries on this disk
  460. pos += 2;
  461. write16_le(records.size(), &buffer[pos]); // Total entries
  462. pos += 2;
  463. write32_le(centralDirSize, &buffer[pos]);
  464. pos += 4;
  465. write32_le(centralDirOffset, &buffer[pos]);
  466. pos += 4;
  467. write16_le(0, &buffer[pos]); // Zip comment length
  468. pos += 2;
  469. writer->writeData(outPos, pos, &buffer[0]);
  470. outPos += pos;
  471. }
  472. }
  473. std::vector<CompressedFilePtr>
  474. zipper::unzip(const ReaderPtr& reader)
  475. {
  476. return readCentralDirectory(reader);
  477. }
  478. bool
  479. zipper::isZip(const ReaderPtr& reader)
  480. {
  481. zsize_t centralDirectoryBytes(0);
  482. zsize_t centralDirectoryOffset(0);
  483. zsize_t centralDirectoryEntries(0);
  484. bool result(
  485. readEndCentralDirectory(
  486. reader,
  487. centralDirectoryBytes,
  488. centralDirectoryOffset,
  489. centralDirectoryEntries
  490. )
  491. );
  492. return result;
  493. }