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