/modules/libjar/zipwriter/src/nsZipWriter.cpp

http://github.com/zpao/v8monkey · C++ · 1073 lines · 800 code · 143 blank · 130 comment · 137 complexity · e609b572f3ffccd468cdf045e4be6a48 MD5 · raw file

  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. *
  4. * The contents of this file are subject to the Mozilla Public License Version
  5. * 1.1 (the "License"); you may not use this file except in compliance with
  6. * the License. You may obtain a copy of the License at
  7. * http://www.mozilla.org/MPL/
  8. *
  9. * Software distributed under the License is distributed on an "AS IS" basis,
  10. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. * for the specific language governing rights and limitations under the
  12. * License.
  13. *
  14. * The Original Code is Zip Writer Component.
  15. *
  16. * The Initial Developer of the Original Code is
  17. * Dave Townsend <dtownsend@oxymoronical.com>.
  18. *
  19. * Portions created by the Initial Developer are Copyright (C) 2007
  20. * the Initial Developer. All Rights Reserved.
  21. *
  22. * Contributor(s):
  23. * Mook <mook.moz+random.code@gmail.com>
  24. *
  25. * Alternatively, the contents of this file may be used under the terms of
  26. * either the GNU General Public License Version 2 or later (the "GPL"), or
  27. * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28. * in which case the provisions of the GPL or the LGPL are applicable instead
  29. * of those above. If you wish to allow use of your version of this file only
  30. * under the terms of either the GPL or the LGPL, and not to allow others to
  31. * use your version of this file under the terms of the MPL, indicate your
  32. * decision by deleting the provisions above and replace them with the notice
  33. * and other provisions required by the GPL or the LGPL. If you do not delete
  34. * the provisions above, a recipient may use your version of this file under
  35. * the terms of any one of the MPL, the GPL or the LGPL.
  36. *
  37. * ***** END LICENSE BLOCK *****
  38. */
  39. #include "StreamFunctions.h"
  40. #include "nsZipWriter.h"
  41. #include "nsZipDataStream.h"
  42. #include "nsISeekableStream.h"
  43. #include "nsIAsyncStreamCopier.h"
  44. #include "nsIStreamListener.h"
  45. #include "nsIInputStreamPump.h"
  46. #include "nsComponentManagerUtils.h"
  47. #include "nsMemory.h"
  48. #include "nsNetError.h"
  49. #include "nsStreamUtils.h"
  50. #include "nsThreadUtils.h"
  51. #include "nsNetUtil.h"
  52. #include "prio.h"
  53. #define ZIP_EOCDR_HEADER_SIZE 22
  54. #define ZIP_EOCDR_HEADER_SIGNATURE 0x06054b50
  55. /**
  56. * nsZipWriter is used to create and add to zip files.
  57. * It is based on the spec available at
  58. * http://www.pkware.com/documents/casestudies/APPNOTE.TXT.
  59. *
  60. * The basic structure of a zip file created is slightly simpler than that
  61. * illustrated in the spec because certain features of the zip format are
  62. * unsupported:
  63. *
  64. * [local file header 1]
  65. * [file data 1]
  66. * .
  67. * .
  68. * .
  69. * [local file header n]
  70. * [file data n]
  71. * [central directory]
  72. * [end of central directory record]
  73. */
  74. NS_IMPL_ISUPPORTS2(nsZipWriter, nsIZipWriter,
  75. nsIRequestObserver)
  76. nsZipWriter::nsZipWriter()
  77. {
  78. mEntryHash.Init();
  79. mInQueue = false;
  80. }
  81. nsZipWriter::~nsZipWriter()
  82. {
  83. if (mStream && !mInQueue)
  84. Close();
  85. }
  86. /* attribute AString comment; */
  87. NS_IMETHODIMP nsZipWriter::GetComment(nsACString & aComment)
  88. {
  89. if (!mStream)
  90. return NS_ERROR_NOT_INITIALIZED;
  91. aComment = mComment;
  92. return NS_OK;
  93. }
  94. NS_IMETHODIMP nsZipWriter::SetComment(const nsACString & aComment)
  95. {
  96. if (!mStream)
  97. return NS_ERROR_NOT_INITIALIZED;
  98. mComment = aComment;
  99. mCDSDirty = true;
  100. return NS_OK;
  101. }
  102. /* readonly attribute boolean inQueue; */
  103. NS_IMETHODIMP nsZipWriter::GetInQueue(bool *aInQueue)
  104. {
  105. *aInQueue = mInQueue;
  106. return NS_OK;
  107. }
  108. /* readonly attribute nsIFile file; */
  109. NS_IMETHODIMP nsZipWriter::GetFile(nsIFile **aFile)
  110. {
  111. if (!mFile)
  112. return NS_ERROR_NOT_INITIALIZED;
  113. nsCOMPtr<nsIFile> file;
  114. nsresult rv = mFile->Clone(getter_AddRefs(file));
  115. NS_ENSURE_SUCCESS(rv, rv);
  116. NS_ADDREF(*aFile = file);
  117. return NS_OK;
  118. }
  119. /*
  120. * Reads file entries out of an existing zip file.
  121. */
  122. nsresult nsZipWriter::ReadFile(nsIFile *aFile)
  123. {
  124. PRInt64 size;
  125. nsresult rv = aFile->GetFileSize(&size);
  126. NS_ENSURE_SUCCESS(rv, rv);
  127. // If the file is too short, it cannot be a valid archive, thus we fail
  128. // without even attempting to open it
  129. NS_ENSURE_TRUE(size > ZIP_EOCDR_HEADER_SIZE, NS_ERROR_FILE_CORRUPTED);
  130. nsCOMPtr<nsIInputStream> inputStream;
  131. rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
  132. NS_ENSURE_SUCCESS(rv, rv);
  133. PRUint8 buf[1024];
  134. PRInt64 seek = size - 1024;
  135. PRUint32 length = 1024;
  136. nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(inputStream);
  137. while (true) {
  138. if (seek < 0) {
  139. length += (PRInt32)seek;
  140. seek = 0;
  141. }
  142. rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, seek);
  143. if (NS_FAILED(rv)) {
  144. inputStream->Close();
  145. return rv;
  146. }
  147. rv = ZW_ReadData(inputStream, (char *)buf, length);
  148. if (NS_FAILED(rv)) {
  149. inputStream->Close();
  150. return rv;
  151. }
  152. /*
  153. * We have to backtrack from the end of the file until we find the
  154. * CDS signature
  155. */
  156. // We know it's at least this far from the end
  157. for (PRUint32 pos = length - ZIP_EOCDR_HEADER_SIZE;
  158. (PRInt32)pos >= 0; pos--) {
  159. PRUint32 sig = PEEK32(buf + pos);
  160. if (sig == ZIP_EOCDR_HEADER_SIGNATURE) {
  161. // Skip down to entry count
  162. pos += 10;
  163. PRUint32 entries = READ16(buf, &pos);
  164. // Skip past CDS size
  165. pos += 4;
  166. mCDSOffset = READ32(buf, &pos);
  167. PRUint32 commentlen = READ16(buf, &pos);
  168. if (commentlen == 0)
  169. mComment.Truncate();
  170. else if (pos + commentlen <= length)
  171. mComment.Assign((const char *)buf + pos, commentlen);
  172. else {
  173. if ((seek + pos + commentlen) > size) {
  174. inputStream->Close();
  175. return NS_ERROR_FILE_CORRUPTED;
  176. }
  177. nsAutoArrayPtr<char> field(new char[commentlen]);
  178. NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
  179. rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
  180. seek + pos);
  181. if (NS_FAILED(rv)) {
  182. inputStream->Close();
  183. return rv;
  184. }
  185. rv = ZW_ReadData(inputStream, field.get(), length);
  186. if (NS_FAILED(rv)) {
  187. inputStream->Close();
  188. return rv;
  189. }
  190. mComment.Assign(field.get(), commentlen);
  191. }
  192. rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
  193. mCDSOffset);
  194. if (NS_FAILED(rv)) {
  195. inputStream->Close();
  196. return rv;
  197. }
  198. for (PRUint32 entry = 0; entry < entries; entry++) {
  199. nsZipHeader* header = new nsZipHeader();
  200. if (!header) {
  201. inputStream->Close();
  202. mEntryHash.Clear();
  203. mHeaders.Clear();
  204. return NS_ERROR_OUT_OF_MEMORY;
  205. }
  206. rv = header->ReadCDSHeader(inputStream);
  207. if (NS_FAILED(rv)) {
  208. inputStream->Close();
  209. mEntryHash.Clear();
  210. mHeaders.Clear();
  211. return rv;
  212. }
  213. if (!mEntryHash.Put(header->mName, mHeaders.Count()))
  214. return NS_ERROR_OUT_OF_MEMORY;
  215. if (!mHeaders.AppendObject(header))
  216. return NS_ERROR_OUT_OF_MEMORY;
  217. }
  218. return inputStream->Close();
  219. }
  220. }
  221. if (seek == 0) {
  222. // We've reached the start with no signature found. Corrupt.
  223. inputStream->Close();
  224. return NS_ERROR_FILE_CORRUPTED;
  225. }
  226. // Overlap by the size of the end of cdr
  227. seek -= (1024 - ZIP_EOCDR_HEADER_SIZE);
  228. }
  229. // Will never reach here in reality
  230. NS_NOTREACHED("Loop should never complete");
  231. return NS_ERROR_UNEXPECTED;
  232. }
  233. /* void open (in nsIFile aFile, in PRInt32 aIoFlags); */
  234. NS_IMETHODIMP nsZipWriter::Open(nsIFile *aFile, PRInt32 aIoFlags)
  235. {
  236. if (mStream)
  237. return NS_ERROR_ALREADY_INITIALIZED;
  238. NS_ENSURE_ARG_POINTER(aFile);
  239. // Need to be able to write to the file
  240. if (aIoFlags & PR_RDONLY)
  241. return NS_ERROR_FAILURE;
  242. nsresult rv = aFile->Clone(getter_AddRefs(mFile));
  243. NS_ENSURE_SUCCESS(rv, rv);
  244. bool exists;
  245. rv = mFile->Exists(&exists);
  246. NS_ENSURE_SUCCESS(rv, rv);
  247. if (!exists && !(aIoFlags & PR_CREATE_FILE))
  248. return NS_ERROR_FILE_NOT_FOUND;
  249. if (exists && !(aIoFlags & (PR_TRUNCATE | PR_WRONLY))) {
  250. rv = ReadFile(mFile);
  251. NS_ENSURE_SUCCESS(rv, rv);
  252. mCDSDirty = false;
  253. }
  254. else {
  255. mCDSOffset = 0;
  256. mCDSDirty = true;
  257. mComment.Truncate();
  258. }
  259. // Silently drop PR_APPEND
  260. aIoFlags &= 0xef;
  261. nsCOMPtr<nsIOutputStream> stream;
  262. rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), mFile, aIoFlags);
  263. if (NS_FAILED(rv)) {
  264. mHeaders.Clear();
  265. mEntryHash.Clear();
  266. return rv;
  267. }
  268. rv = NS_NewBufferedOutputStream(getter_AddRefs(mStream), stream, 64 * 1024);
  269. if (NS_FAILED(rv)) {
  270. stream->Close();
  271. mHeaders.Clear();
  272. mEntryHash.Clear();
  273. return rv;
  274. }
  275. if (mCDSOffset > 0) {
  276. rv = SeekCDS();
  277. NS_ENSURE_SUCCESS(rv, rv);
  278. }
  279. return NS_OK;
  280. }
  281. /* nsIZipEntry getEntry (in AString aZipEntry); */
  282. NS_IMETHODIMP nsZipWriter::GetEntry(const nsACString & aZipEntry,
  283. nsIZipEntry **_retval)
  284. {
  285. PRInt32 pos;
  286. if (mEntryHash.Get(aZipEntry, &pos))
  287. NS_ADDREF(*_retval = mHeaders[pos]);
  288. else
  289. *_retval = nsnull;
  290. return NS_OK;
  291. }
  292. /* boolean hasEntry (in AString aZipEntry); */
  293. NS_IMETHODIMP nsZipWriter::HasEntry(const nsACString & aZipEntry,
  294. bool *_retval)
  295. {
  296. *_retval = mEntryHash.Get(aZipEntry, nsnull);
  297. return NS_OK;
  298. }
  299. /* void addEntryDirectory (in AUTF8String aZipEntry, in PRTime aModTime,
  300. * in boolean aQueue); */
  301. NS_IMETHODIMP nsZipWriter::AddEntryDirectory(const nsACString & aZipEntry,
  302. PRTime aModTime, bool aQueue)
  303. {
  304. if (!mStream)
  305. return NS_ERROR_NOT_INITIALIZED;
  306. if (aQueue) {
  307. nsZipQueueItem item;
  308. item.mOperation = OPERATION_ADD;
  309. item.mZipEntry = aZipEntry;
  310. item.mModTime = aModTime;
  311. item.mPermissions = PERMISSIONS_DIR;
  312. if (!mQueue.AppendElement(item))
  313. return NS_ERROR_OUT_OF_MEMORY;
  314. return NS_OK;
  315. }
  316. if (mInQueue)
  317. return NS_ERROR_IN_PROGRESS;
  318. return InternalAddEntryDirectory(aZipEntry, aModTime, PERMISSIONS_DIR);
  319. }
  320. /* void addEntryFile (in AUTF8String aZipEntry, in PRInt32 aCompression,
  321. * in nsIFile aFile, in boolean aQueue); */
  322. NS_IMETHODIMP nsZipWriter::AddEntryFile(const nsACString & aZipEntry,
  323. PRInt32 aCompression, nsIFile *aFile,
  324. bool aQueue)
  325. {
  326. NS_ENSURE_ARG_POINTER(aFile);
  327. if (!mStream)
  328. return NS_ERROR_NOT_INITIALIZED;
  329. nsresult rv;
  330. if (aQueue) {
  331. nsZipQueueItem item;
  332. item.mOperation = OPERATION_ADD;
  333. item.mZipEntry = aZipEntry;
  334. item.mCompression = aCompression;
  335. rv = aFile->Clone(getter_AddRefs(item.mFile));
  336. NS_ENSURE_SUCCESS(rv, rv);
  337. if (!mQueue.AppendElement(item))
  338. return NS_ERROR_OUT_OF_MEMORY;
  339. return NS_OK;
  340. }
  341. if (mInQueue)
  342. return NS_ERROR_IN_PROGRESS;
  343. bool exists;
  344. rv = aFile->Exists(&exists);
  345. NS_ENSURE_SUCCESS(rv, rv);
  346. if (!exists)
  347. return NS_ERROR_FILE_NOT_FOUND;
  348. bool isdir;
  349. rv = aFile->IsDirectory(&isdir);
  350. NS_ENSURE_SUCCESS(rv, rv);
  351. PRInt64 modtime;
  352. rv = aFile->GetLastModifiedTime(&modtime);
  353. NS_ENSURE_SUCCESS(rv, rv);
  354. modtime *= PR_USEC_PER_MSEC;
  355. PRUint32 permissions;
  356. rv = aFile->GetPermissions(&permissions);
  357. NS_ENSURE_SUCCESS(rv, rv);
  358. if (isdir)
  359. return InternalAddEntryDirectory(aZipEntry, modtime, permissions);
  360. if (mEntryHash.Get(aZipEntry, nsnull))
  361. return NS_ERROR_FILE_ALREADY_EXISTS;
  362. nsCOMPtr<nsIInputStream> inputStream;
  363. rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
  364. aFile);
  365. NS_ENSURE_SUCCESS(rv, rv);
  366. rv = AddEntryStream(aZipEntry, modtime, aCompression, inputStream,
  367. false, permissions);
  368. NS_ENSURE_SUCCESS(rv, rv);
  369. return inputStream->Close();
  370. }
  371. /* void addEntryChannel (in AUTF8String aZipEntry, in PRTime aModTime,
  372. * in PRInt32 aCompression, in nsIChannel aChannel,
  373. * in boolean aQueue); */
  374. NS_IMETHODIMP nsZipWriter::AddEntryChannel(const nsACString & aZipEntry,
  375. PRTime aModTime,
  376. PRInt32 aCompression,
  377. nsIChannel *aChannel,
  378. bool aQueue)
  379. {
  380. NS_ENSURE_ARG_POINTER(aChannel);
  381. if (!mStream)
  382. return NS_ERROR_NOT_INITIALIZED;
  383. if (aQueue) {
  384. nsZipQueueItem item;
  385. item.mOperation = OPERATION_ADD;
  386. item.mZipEntry = aZipEntry;
  387. item.mModTime = aModTime;
  388. item.mCompression = aCompression;
  389. item.mPermissions = PERMISSIONS_FILE;
  390. item.mChannel = aChannel;
  391. if (!mQueue.AppendElement(item))
  392. return NS_ERROR_OUT_OF_MEMORY;
  393. return NS_OK;
  394. }
  395. if (mInQueue)
  396. return NS_ERROR_IN_PROGRESS;
  397. if (mEntryHash.Get(aZipEntry, nsnull))
  398. return NS_ERROR_FILE_ALREADY_EXISTS;
  399. nsCOMPtr<nsIInputStream> inputStream;
  400. nsresult rv = aChannel->Open(getter_AddRefs(inputStream));
  401. NS_ENSURE_SUCCESS(rv, rv);
  402. rv = AddEntryStream(aZipEntry, aModTime, aCompression, inputStream,
  403. false, PERMISSIONS_FILE);
  404. NS_ENSURE_SUCCESS(rv, rv);
  405. return inputStream->Close();
  406. }
  407. /* void addEntryStream (in AUTF8String aZipEntry, in PRTime aModTime,
  408. * in PRInt32 aCompression, in nsIInputStream aStream,
  409. * in boolean aQueue); */
  410. NS_IMETHODIMP nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
  411. PRTime aModTime,
  412. PRInt32 aCompression,
  413. nsIInputStream *aStream,
  414. bool aQueue)
  415. {
  416. return AddEntryStream(aZipEntry, aModTime, aCompression, aStream, aQueue,
  417. PERMISSIONS_FILE);
  418. }
  419. /* void addEntryStream (in AUTF8String aZipEntry, in PRTime aModTime,
  420. * in PRInt32 aCompression, in nsIInputStream aStream,
  421. * in boolean aQueue, in unsigned long aPermissions); */
  422. nsresult nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
  423. PRTime aModTime,
  424. PRInt32 aCompression,
  425. nsIInputStream *aStream,
  426. bool aQueue,
  427. PRUint32 aPermissions)
  428. {
  429. NS_ENSURE_ARG_POINTER(aStream);
  430. if (!mStream)
  431. return NS_ERROR_NOT_INITIALIZED;
  432. if (aQueue) {
  433. nsZipQueueItem item;
  434. item.mOperation = OPERATION_ADD;
  435. item.mZipEntry = aZipEntry;
  436. item.mModTime = aModTime;
  437. item.mCompression = aCompression;
  438. item.mPermissions = aPermissions;
  439. item.mStream = aStream;
  440. if (!mQueue.AppendElement(item))
  441. return NS_ERROR_OUT_OF_MEMORY;
  442. return NS_OK;
  443. }
  444. if (mInQueue)
  445. return NS_ERROR_IN_PROGRESS;
  446. if (mEntryHash.Get(aZipEntry, nsnull))
  447. return NS_ERROR_FILE_ALREADY_EXISTS;
  448. nsRefPtr<nsZipHeader> header = new nsZipHeader();
  449. NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
  450. header->Init(aZipEntry, aModTime, ZIP_ATTRS(aPermissions, ZIP_ATTRS_FILE),
  451. mCDSOffset);
  452. nsresult rv = header->WriteFileHeader(mStream);
  453. if (NS_FAILED(rv)) {
  454. SeekCDS();
  455. return rv;
  456. }
  457. nsRefPtr<nsZipDataStream> stream = new nsZipDataStream();
  458. if (!stream) {
  459. SeekCDS();
  460. return NS_ERROR_OUT_OF_MEMORY;
  461. }
  462. rv = stream->Init(this, mStream, header, aCompression);
  463. if (NS_FAILED(rv)) {
  464. SeekCDS();
  465. return rv;
  466. }
  467. rv = stream->ReadStream(aStream);
  468. if (NS_FAILED(rv))
  469. SeekCDS();
  470. return rv;
  471. }
  472. /* void removeEntry (in AUTF8String aZipEntry, in boolean aQueue); */
  473. NS_IMETHODIMP nsZipWriter::RemoveEntry(const nsACString & aZipEntry,
  474. bool aQueue)
  475. {
  476. if (!mStream)
  477. return NS_ERROR_NOT_INITIALIZED;
  478. if (aQueue) {
  479. nsZipQueueItem item;
  480. item.mOperation = OPERATION_REMOVE;
  481. item.mZipEntry = aZipEntry;
  482. if (!mQueue.AppendElement(item))
  483. return NS_ERROR_OUT_OF_MEMORY;
  484. return NS_OK;
  485. }
  486. if (mInQueue)
  487. return NS_ERROR_IN_PROGRESS;
  488. PRInt32 pos;
  489. if (mEntryHash.Get(aZipEntry, &pos)) {
  490. // Flush any remaining data before we seek.
  491. nsresult rv = mStream->Flush();
  492. NS_ENSURE_SUCCESS(rv, rv);
  493. if (pos < mHeaders.Count() - 1) {
  494. // This is not the last entry, pull back the data.
  495. nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
  496. rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
  497. mHeaders[pos]->mOffset);
  498. NS_ENSURE_SUCCESS(rv, rv);
  499. nsCOMPtr<nsIInputStream> inputStream;
  500. rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
  501. mFile);
  502. NS_ENSURE_SUCCESS(rv, rv);
  503. seekable = do_QueryInterface(inputStream);
  504. rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
  505. mHeaders[pos + 1]->mOffset);
  506. if (NS_FAILED(rv)) {
  507. inputStream->Close();
  508. return rv;
  509. }
  510. PRUint32 count = mCDSOffset - mHeaders[pos + 1]->mOffset;
  511. PRUint32 read = 0;
  512. char buf[4096];
  513. while (count > 0) {
  514. if (count < sizeof(buf))
  515. read = count;
  516. else
  517. read = sizeof(buf);
  518. rv = inputStream->Read(buf, read, &read);
  519. if (NS_FAILED(rv)) {
  520. inputStream->Close();
  521. Cleanup();
  522. return rv;
  523. }
  524. rv = ZW_WriteData(mStream, buf, read);
  525. if (NS_FAILED(rv)) {
  526. inputStream->Close();
  527. Cleanup();
  528. return rv;
  529. }
  530. count -= read;
  531. }
  532. inputStream->Close();
  533. // Rewrite header offsets and update hash
  534. PRUint32 shift = (mHeaders[pos + 1]->mOffset -
  535. mHeaders[pos]->mOffset);
  536. mCDSOffset -= shift;
  537. PRInt32 pos2 = pos + 1;
  538. while (pos2 < mHeaders.Count()) {
  539. if (!mEntryHash.Put(mHeaders[pos2]->mName, pos2-1)) {
  540. Cleanup();
  541. return NS_ERROR_OUT_OF_MEMORY;
  542. }
  543. mHeaders[pos2]->mOffset -= shift;
  544. pos2++;
  545. }
  546. }
  547. else {
  548. // Remove the last entry is just a case of moving the CDS
  549. mCDSOffset = mHeaders[pos]->mOffset;
  550. rv = SeekCDS();
  551. NS_ENSURE_SUCCESS(rv, rv);
  552. }
  553. mEntryHash.Remove(mHeaders[pos]->mName);
  554. mHeaders.RemoveObjectAt(pos);
  555. mCDSDirty = true;
  556. return NS_OK;
  557. }
  558. return NS_ERROR_FILE_NOT_FOUND;
  559. }
  560. /* void processQueue (in nsIRequestObserver aObserver,
  561. * in nsISupports aContext); */
  562. NS_IMETHODIMP nsZipWriter::ProcessQueue(nsIRequestObserver *aObserver,
  563. nsISupports *aContext)
  564. {
  565. if (!mStream)
  566. return NS_ERROR_NOT_INITIALIZED;
  567. if (mInQueue)
  568. return NS_ERROR_IN_PROGRESS;
  569. mProcessObserver = aObserver;
  570. mProcessContext = aContext;
  571. mInQueue = true;
  572. if (mProcessObserver)
  573. mProcessObserver->OnStartRequest(nsnull, mProcessContext);
  574. BeginProcessingNextItem();
  575. return NS_OK;
  576. }
  577. /* void close (); */
  578. NS_IMETHODIMP nsZipWriter::Close()
  579. {
  580. if (!mStream)
  581. return NS_ERROR_NOT_INITIALIZED;
  582. if (mInQueue)
  583. return NS_ERROR_IN_PROGRESS;
  584. if (mCDSDirty) {
  585. PRUint32 size = 0;
  586. for (PRInt32 i = 0; i < mHeaders.Count(); i++) {
  587. nsresult rv = mHeaders[i]->WriteCDSHeader(mStream);
  588. if (NS_FAILED(rv)) {
  589. Cleanup();
  590. return rv;
  591. }
  592. size += mHeaders[i]->GetCDSHeaderLength();
  593. }
  594. PRUint8 buf[ZIP_EOCDR_HEADER_SIZE];
  595. PRUint32 pos = 0;
  596. WRITE32(buf, &pos, ZIP_EOCDR_HEADER_SIGNATURE);
  597. WRITE16(buf, &pos, 0);
  598. WRITE16(buf, &pos, 0);
  599. WRITE16(buf, &pos, mHeaders.Count());
  600. WRITE16(buf, &pos, mHeaders.Count());
  601. WRITE32(buf, &pos, size);
  602. WRITE32(buf, &pos, mCDSOffset);
  603. WRITE16(buf, &pos, mComment.Length());
  604. nsresult rv = ZW_WriteData(mStream, (const char *)buf, pos);
  605. if (NS_FAILED(rv)) {
  606. Cleanup();
  607. return rv;
  608. }
  609. rv = ZW_WriteData(mStream, mComment.get(), mComment.Length());
  610. if (NS_FAILED(rv)) {
  611. Cleanup();
  612. return rv;
  613. }
  614. nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
  615. rv = seekable->SetEOF();
  616. if (NS_FAILED(rv)) {
  617. Cleanup();
  618. return rv;
  619. }
  620. // Go back and rewrite the file headers
  621. for (PRInt32 i = 0; i < mHeaders.Count(); i++) {
  622. nsZipHeader *header = mHeaders[i];
  623. if (!header->mWriteOnClose)
  624. continue;
  625. rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, header->mOffset);
  626. if (NS_FAILED(rv)) {
  627. Cleanup();
  628. return rv;
  629. }
  630. rv = header->WriteFileHeader(mStream);
  631. if (NS_FAILED(rv)) {
  632. Cleanup();
  633. return rv;
  634. }
  635. }
  636. }
  637. nsresult rv = mStream->Close();
  638. mStream = nsnull;
  639. mHeaders.Clear();
  640. mEntryHash.Clear();
  641. mQueue.Clear();
  642. return rv;
  643. }
  644. // Our nsIRequestObserver monitors removal operations performed on the queue
  645. /* void onStartRequest (in nsIRequest aRequest, in nsISupports aContext); */
  646. NS_IMETHODIMP nsZipWriter::OnStartRequest(nsIRequest *aRequest,
  647. nsISupports *aContext)
  648. {
  649. return NS_OK;
  650. }
  651. /* void onStopRequest (in nsIRequest aRequest, in nsISupports aContext,
  652. * in nsresult aStatusCode); */
  653. NS_IMETHODIMP nsZipWriter::OnStopRequest(nsIRequest *aRequest,
  654. nsISupports *aContext,
  655. nsresult aStatusCode)
  656. {
  657. if (NS_FAILED(aStatusCode)) {
  658. FinishQueue(aStatusCode);
  659. Cleanup();
  660. }
  661. nsresult rv = mStream->Flush();
  662. if (NS_FAILED(rv)) {
  663. FinishQueue(rv);
  664. Cleanup();
  665. return rv;
  666. }
  667. rv = SeekCDS();
  668. if (NS_FAILED(rv)) {
  669. FinishQueue(rv);
  670. return rv;
  671. }
  672. BeginProcessingNextItem();
  673. return NS_OK;
  674. }
  675. nsresult nsZipWriter::InternalAddEntryDirectory(const nsACString & aZipEntry,
  676. PRTime aModTime,
  677. PRUint32 aPermissions)
  678. {
  679. nsRefPtr<nsZipHeader> header = new nsZipHeader();
  680. NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
  681. PRUint32 zipAttributes = ZIP_ATTRS(aPermissions, ZIP_ATTRS_DIRECTORY);
  682. if (aZipEntry.Last() != '/') {
  683. nsCString dirPath;
  684. dirPath.Assign(aZipEntry + NS_LITERAL_CSTRING("/"));
  685. header->Init(dirPath, aModTime, zipAttributes, mCDSOffset);
  686. }
  687. else
  688. header->Init(aZipEntry, aModTime, zipAttributes, mCDSOffset);
  689. if (mEntryHash.Get(header->mName, nsnull))
  690. return NS_ERROR_FILE_ALREADY_EXISTS;
  691. nsresult rv = header->WriteFileHeader(mStream);
  692. if (NS_FAILED(rv)) {
  693. Cleanup();
  694. return rv;
  695. }
  696. mCDSDirty = true;
  697. mCDSOffset += header->GetFileHeaderLength();
  698. if (!mEntryHash.Put(header->mName, mHeaders.Count())) {
  699. Cleanup();
  700. return NS_ERROR_OUT_OF_MEMORY;
  701. }
  702. if (!mHeaders.AppendObject(header)) {
  703. Cleanup();
  704. return NS_ERROR_OUT_OF_MEMORY;
  705. }
  706. return NS_OK;
  707. }
  708. /*
  709. * Recovering from an error while adding a new entry is simply a case of
  710. * seeking back to the CDS. If we fail trying to do that though then cleanup
  711. * and bail out.
  712. */
  713. nsresult nsZipWriter::SeekCDS()
  714. {
  715. nsresult rv;
  716. nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv);
  717. if (NS_FAILED(rv)) {
  718. Cleanup();
  719. return rv;
  720. }
  721. rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mCDSOffset);
  722. if (NS_FAILED(rv))
  723. Cleanup();
  724. return rv;
  725. }
  726. /*
  727. * In a bad error condition this essentially closes down the component as best
  728. * it can.
  729. */
  730. void nsZipWriter::Cleanup()
  731. {
  732. mHeaders.Clear();
  733. mEntryHash.Clear();
  734. if (mStream)
  735. mStream->Close();
  736. mStream = nsnull;
  737. mFile = nsnull;
  738. }
  739. /*
  740. * Called when writing a file to the zip is complete.
  741. */
  742. nsresult nsZipWriter::EntryCompleteCallback(nsZipHeader* aHeader,
  743. nsresult aStatus)
  744. {
  745. if (NS_SUCCEEDED(aStatus)) {
  746. if (!mEntryHash.Put(aHeader->mName, mHeaders.Count())) {
  747. SeekCDS();
  748. return NS_ERROR_OUT_OF_MEMORY;
  749. }
  750. if (!mHeaders.AppendObject(aHeader)) {
  751. mEntryHash.Remove(aHeader->mName);
  752. SeekCDS();
  753. return NS_ERROR_OUT_OF_MEMORY;
  754. }
  755. mCDSDirty = true;
  756. mCDSOffset += aHeader->mCSize + aHeader->GetFileHeaderLength();
  757. if (mInQueue)
  758. BeginProcessingNextItem();
  759. return NS_OK;
  760. }
  761. nsresult rv = SeekCDS();
  762. if (mInQueue)
  763. FinishQueue(aStatus);
  764. return rv;
  765. }
  766. inline nsresult nsZipWriter::BeginProcessingAddition(nsZipQueueItem* aItem,
  767. bool* complete)
  768. {
  769. if (aItem->mFile) {
  770. bool exists;
  771. nsresult rv = aItem->mFile->Exists(&exists);
  772. NS_ENSURE_SUCCESS(rv, rv);
  773. if (!exists) return NS_ERROR_FILE_NOT_FOUND;
  774. bool isdir;
  775. rv = aItem->mFile->IsDirectory(&isdir);
  776. NS_ENSURE_SUCCESS(rv, rv);
  777. rv = aItem->mFile->GetLastModifiedTime(&aItem->mModTime);
  778. NS_ENSURE_SUCCESS(rv, rv);
  779. aItem->mModTime *= PR_USEC_PER_MSEC;
  780. rv = aItem->mFile->GetPermissions(&aItem->mPermissions);
  781. NS_ENSURE_SUCCESS(rv, rv);
  782. if (!isdir) {
  783. // Set up for fall through to stream reader
  784. rv = NS_NewLocalFileInputStream(getter_AddRefs(aItem->mStream),
  785. aItem->mFile);
  786. NS_ENSURE_SUCCESS(rv, rv);
  787. }
  788. // If a dir then this will fall through to the plain dir addition
  789. }
  790. PRUint32 zipAttributes = ZIP_ATTRS(aItem->mPermissions, ZIP_ATTRS_FILE);
  791. if (aItem->mStream || aItem->mChannel) {
  792. nsRefPtr<nsZipHeader> header = new nsZipHeader();
  793. NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
  794. header->Init(aItem->mZipEntry, aItem->mModTime, zipAttributes,
  795. mCDSOffset);
  796. nsresult rv = header->WriteFileHeader(mStream);
  797. NS_ENSURE_SUCCESS(rv, rv);
  798. nsRefPtr<nsZipDataStream> stream = new nsZipDataStream();
  799. NS_ENSURE_TRUE(stream, NS_ERROR_OUT_OF_MEMORY);
  800. rv = stream->Init(this, mStream, header, aItem->mCompression);
  801. NS_ENSURE_SUCCESS(rv, rv);
  802. if (aItem->mStream) {
  803. nsCOMPtr<nsIInputStreamPump> pump;
  804. rv = NS_NewInputStreamPump(getter_AddRefs(pump), aItem->mStream,
  805. -1, -1, 0, 0, true);
  806. NS_ENSURE_SUCCESS(rv, rv);
  807. rv = pump->AsyncRead(stream, nsnull);
  808. NS_ENSURE_SUCCESS(rv, rv);
  809. }
  810. else {
  811. rv = aItem->mChannel->AsyncOpen(stream, nsnull);
  812. NS_ENSURE_SUCCESS(rv, rv);
  813. }
  814. return NS_OK;
  815. }
  816. // Must be plain directory addition
  817. *complete = true;
  818. return InternalAddEntryDirectory(aItem->mZipEntry, aItem->mModTime,
  819. aItem->mPermissions);
  820. }
  821. inline nsresult nsZipWriter::BeginProcessingRemoval(PRInt32 aPos)
  822. {
  823. // Open the zip file for reading
  824. nsCOMPtr<nsIInputStream> inputStream;
  825. nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
  826. mFile);
  827. NS_ENSURE_SUCCESS(rv, rv);
  828. nsCOMPtr<nsIInputStreamPump> pump;
  829. rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream, -1, -1, 0,
  830. 0, true);
  831. if (NS_FAILED(rv)) {
  832. inputStream->Close();
  833. return rv;
  834. }
  835. nsCOMPtr<nsIStreamListener> listener;
  836. rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), mStream, this);
  837. if (NS_FAILED(rv)) {
  838. inputStream->Close();
  839. return rv;
  840. }
  841. nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
  842. rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
  843. mHeaders[aPos]->mOffset);
  844. if (NS_FAILED(rv)) {
  845. inputStream->Close();
  846. return rv;
  847. }
  848. PRUint32 shift = (mHeaders[aPos + 1]->mOffset -
  849. mHeaders[aPos]->mOffset);
  850. mCDSOffset -= shift;
  851. PRInt32 pos2 = aPos + 1;
  852. while (pos2 < mHeaders.Count()) {
  853. mEntryHash.Put(mHeaders[pos2]->mName, pos2 - 1);
  854. mHeaders[pos2]->mOffset -= shift;
  855. pos2++;
  856. }
  857. mEntryHash.Remove(mHeaders[aPos]->mName);
  858. mHeaders.RemoveObjectAt(aPos);
  859. mCDSDirty = true;
  860. rv = pump->AsyncRead(listener, nsnull);
  861. if (NS_FAILED(rv)) {
  862. inputStream->Close();
  863. Cleanup();
  864. return rv;
  865. }
  866. return NS_OK;
  867. }
  868. /*
  869. * Starts processing on the next item in the queue.
  870. */
  871. void nsZipWriter::BeginProcessingNextItem()
  872. {
  873. while (!mQueue.IsEmpty()) {
  874. nsZipQueueItem next = mQueue[0];
  875. mQueue.RemoveElementAt(0);
  876. if (next.mOperation == OPERATION_REMOVE) {
  877. PRInt32 pos = -1;
  878. if (mEntryHash.Get(next.mZipEntry, &pos)) {
  879. if (pos < mHeaders.Count() - 1) {
  880. nsresult rv = BeginProcessingRemoval(pos);
  881. if (NS_FAILED(rv)) FinishQueue(rv);
  882. return;
  883. }
  884. mCDSOffset = mHeaders[pos]->mOffset;
  885. nsresult rv = SeekCDS();
  886. if (NS_FAILED(rv)) {
  887. FinishQueue(rv);
  888. return;
  889. }
  890. mEntryHash.Remove(mHeaders[pos]->mName);
  891. mHeaders.RemoveObjectAt(pos);
  892. }
  893. else {
  894. FinishQueue(NS_ERROR_FILE_NOT_FOUND);
  895. return;
  896. }
  897. }
  898. else if (next.mOperation == OPERATION_ADD) {
  899. if (mEntryHash.Get(next.mZipEntry, nsnull)) {
  900. FinishQueue(NS_ERROR_FILE_ALREADY_EXISTS);
  901. return;
  902. }
  903. bool complete = false;
  904. nsresult rv = BeginProcessingAddition(&next, &complete);
  905. if (NS_FAILED(rv)) {
  906. SeekCDS();
  907. FinishQueue(rv);
  908. return;
  909. }
  910. if (!complete)
  911. return;
  912. }
  913. }
  914. FinishQueue(NS_OK);
  915. }
  916. /*
  917. * Ends processing with the given status.
  918. */
  919. void nsZipWriter::FinishQueue(nsresult aStatus)
  920. {
  921. nsCOMPtr<nsIRequestObserver> observer = mProcessObserver;
  922. nsCOMPtr<nsISupports> context = mProcessContext;
  923. // Clean up everything first in case the observer decides to queue more
  924. // things
  925. mProcessObserver = nsnull;
  926. mProcessContext = nsnull;
  927. mInQueue = false;
  928. if (observer)
  929. observer->OnStopRequest(nsnull, context, aStatus);
  930. }