summaryrefslogtreecommitdiffstats
path: root/dom/archivereader
diff options
context:
space:
mode:
Diffstat (limited to 'dom/archivereader')
-rw-r--r--dom/archivereader/ArchiveEvent.cpp131
-rw-r--r--dom/archivereader/ArchiveEvent.h84
-rw-r--r--dom/archivereader/ArchiveReader.cpp217
-rw-r--r--dom/archivereader/ArchiveReader.h119
-rw-r--r--dom/archivereader/ArchiveReaderCommon.h24
-rw-r--r--dom/archivereader/ArchiveRequest.cpp277
-rw-r--r--dom/archivereader/ArchiveRequest.h88
-rw-r--r--dom/archivereader/ArchiveZipEvent.cpp216
-rw-r--r--dom/archivereader/ArchiveZipEvent.h70
-rw-r--r--dom/archivereader/ArchiveZipFile.cpp402
-rw-r--r--dom/archivereader/ArchiveZipFile.h81
-rw-r--r--dom/archivereader/moz.build30
-rw-r--r--dom/archivereader/test/helpers.js31
-rw-r--r--dom/archivereader/test/mochitest.ini7
-rw-r--r--dom/archivereader/test/test_basic.html227
-rw-r--r--dom/archivereader/test/test_nonUnicode.html77
-rw-r--r--dom/archivereader/test/test_zip_in_zip.html111
17 files changed, 2192 insertions, 0 deletions
diff --git a/dom/archivereader/ArchiveEvent.cpp b/dom/archivereader/ArchiveEvent.cpp
new file mode 100644
index 000000000..b1b3dcd82
--- /dev/null
+++ b/dom/archivereader/ArchiveEvent.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ArchiveEvent.h"
+
+#include "nsCExternalHandlerService.h"
+#include "nsProxyRelease.h"
+
+USING_ARCHIVEREADER_NAMESPACE
+
+NS_IMPL_ISUPPORTS0(ArchiveItem)
+
+ArchiveItem::ArchiveItem()
+{
+ MOZ_COUNT_CTOR(ArchiveItem);
+}
+
+ArchiveItem::~ArchiveItem()
+{
+ MOZ_COUNT_DTOR(ArchiveItem);
+}
+
+
+nsCString
+ArchiveItem::GetType()
+{
+ if (mType.IsEmpty()) {
+ return NS_LITERAL_CSTRING("binary/octet-stream");
+ }
+
+ return mType;
+}
+
+void
+ArchiveItem::SetType(const nsCString& aType)
+{
+ mType = aType;
+}
+
+ArchiveReaderEvent::ArchiveReaderEvent(ArchiveReader* aArchiveReader)
+: mArchiveReader(aArchiveReader)
+{
+ MOZ_COUNT_CTOR(ArchiveReaderEvent);
+}
+
+ArchiveReaderEvent::~ArchiveReaderEvent()
+{
+ if (!NS_IsMainThread()) {
+ NS_ReleaseOnMainThread(mMimeService.forget());
+ }
+
+ MOZ_COUNT_DTOR(ArchiveReaderEvent);
+}
+
+// From the filename to the mimetype:
+nsresult
+ArchiveReaderEvent::GetType(nsCString& aExt,
+ nsCString& aMimeType)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ if (mMimeService.get() == nullptr) {
+ mMimeService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mMimeService->GetTypeFromExtension(aExt, aMimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveReaderEvent::Run()
+{
+ return Exec();
+}
+
+nsresult
+ArchiveReaderEvent::RunShare(nsresult aStatus)
+{
+ mStatus = aStatus;
+
+ NS_DispatchToMainThread(NewRunnableMethod(this, &ArchiveReaderEvent::ShareMainThread));
+
+ return NS_OK;
+}
+
+void
+ArchiveReaderEvent::ShareMainThread()
+{
+ nsTArray<RefPtr<File>> fileList;
+
+ if (!NS_FAILED(mStatus)) {
+ // This extra step must run in the main thread:
+ for (uint32_t index = 0; index < mFileList.Length(); ++index) {
+ RefPtr<ArchiveItem> item = mFileList[index];
+
+ nsString tmp;
+ nsresult rv = item->GetFilename(tmp);
+ nsCString filename = NS_ConvertUTF16toUTF8(tmp);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ int32_t offset = filename.RFindChar('.');
+ if (offset != kNotFound) {
+ filename.Cut(0, offset + 1);
+
+ // Just to be sure, if something goes wrong, the mimetype is an empty string:
+ nsCString type;
+ if (NS_SUCCEEDED(GetType(filename, type))) {
+ item->SetType(type);
+ }
+ }
+
+ // This is a File:
+ RefPtr<File> file = item->GetFile(mArchiveReader);
+ if (file) {
+ fileList.AppendElement(file);
+ }
+ }
+ }
+
+ mArchiveReader->Ready(fileList, mStatus);
+}
diff --git a/dom/archivereader/ArchiveEvent.h b/dom/archivereader/ArchiveEvent.h
new file mode 100644
index 000000000..92ac2774d
--- /dev/null
+++ b/dom/archivereader/ArchiveEvent.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_archivereader_domarchiveevent_h__
+#define mozilla_dom_archivereader_domarchiveevent_h__
+
+#include "ArchiveReader.h"
+
+#include "mozilla/dom/File.h"
+#include "nsISeekableStream.h"
+#include "nsIMIMEService.h"
+
+#include "ArchiveReaderCommon.h"
+
+BEGIN_ARCHIVEREADER_NAMESPACE
+
+/**
+ * This class contains all the info needed for a single item
+ * It must contain the implementation of the File() method.
+ */
+class ArchiveItem : public nsISupports
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ ArchiveItem();
+
+ // Getter/Setter for the type
+ nsCString GetType();
+ void SetType(const nsCString& aType);
+
+ // Getter for the filename
+ virtual nsresult GetFilename(nsString& aFilename) = 0;
+
+ // Generate a File
+ virtual already_AddRefed<File> GetFile(ArchiveReader* aArchiveReader) = 0;
+
+protected:
+ virtual ~ArchiveItem();
+
+ nsCString mType;
+};
+
+/**
+ * This class must be extended by any archive format supported by ArchiveReader API
+ * This class runs in a different thread and it calls the 'exec()' method.
+ * The exec() must populate mFileList and mStatus then it must call RunShare();
+ */
+class ArchiveReaderEvent : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ explicit ArchiveReaderEvent(ArchiveReader* aArchiveReader);
+
+protected:
+ virtual ~ArchiveReaderEvent();
+
+public:
+ // This must be implemented
+ virtual nsresult Exec() = 0;
+
+protected:
+ nsresult GetType(nsCString& aExt,
+ nsCString& aMimeType);
+
+ nsresult RunShare(nsresult aStatus);
+ void ShareMainThread();
+
+protected: // data
+ ArchiveReader* mArchiveReader;
+
+ nsCOMPtr<nsIMIMEService> mMimeService;
+
+ nsTArray<RefPtr<ArchiveItem> > mFileList; // this must be populated
+ nsresult mStatus;
+};
+
+END_ARCHIVEREADER_NAMESPACE
+
+#endif // mozilla_dom_archivereader_domarchiveevent_h__
diff --git a/dom/archivereader/ArchiveReader.cpp b/dom/archivereader/ArchiveReader.cpp
new file mode 100644
index 000000000..f6985d989
--- /dev/null
+++ b/dom/archivereader/ArchiveReader.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ArchiveReader.h"
+#include "ArchiveRequest.h"
+#include "ArchiveEvent.h"
+#include "ArchiveZipEvent.h"
+
+#include "nsIURI.h"
+#include "nsNetCID.h"
+
+#include "mozilla/dom/ArchiveReaderBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+USING_ARCHIVEREADER_NAMESPACE
+
+/* static */ already_AddRefed<ArchiveReader>
+ArchiveReader::Constructor(const GlobalObject& aGlobal,
+ Blob& aBlob,
+ const ArchiveReaderOptions& aOptions,
+ ErrorResult& aError)
+{
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(aOptions.mEncoding,
+ encoding)) {
+ aError.ThrowRangeError<MSG_ENCODING_NOT_SUPPORTED>(aOptions.mEncoding);
+ return nullptr;
+ }
+
+ RefPtr<ArchiveReader> reader =
+ new ArchiveReader(aBlob, window, encoding);
+ return reader.forget();
+}
+
+ArchiveReader::ArchiveReader(Blob& aBlob, nsPIDOMWindowInner* aWindow,
+ const nsACString& aEncoding)
+ : mBlobImpl(aBlob.Impl())
+ , mWindow(aWindow)
+ , mStatus(NOT_STARTED)
+ , mEncoding(aEncoding)
+{
+ MOZ_ASSERT(aWindow);
+}
+
+ArchiveReader::~ArchiveReader()
+{
+}
+
+/* virtual */ JSObject*
+ArchiveReader::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ArchiveReaderBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult
+ArchiveReader::RegisterRequest(ArchiveRequest* aRequest)
+{
+ switch (mStatus) {
+ // Append to the list and let's start to work:
+ case NOT_STARTED:
+ mRequests.AppendElement(aRequest);
+ return OpenArchive();
+
+ // Just append to the list:
+ case WORKING:
+ mRequests.AppendElement(aRequest);
+ return NS_OK;
+
+ // Return data!
+ case READY:
+ RequestReady(aRequest);
+ return NS_OK;
+ }
+
+ NS_ASSERTION(false, "unexpected mStatus value");
+ return NS_OK;
+}
+
+// This returns the input stream
+nsresult
+ArchiveReader::GetInputStream(nsIInputStream** aInputStream)
+{
+ // Getting the input stream
+ ErrorResult rv;
+ mBlobImpl->GetInternalStream(aInputStream, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ArchiveReader::GetSize(uint64_t* aSize)
+{
+ ErrorResult rv;
+ *aSize = mBlobImpl->GetSize(rv);
+ return rv.StealNSResult();
+}
+
+// Here we open the archive:
+nsresult
+ArchiveReader::OpenArchive()
+{
+ mStatus = WORKING;
+ nsresult rv;
+
+ // Target:
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ NS_ASSERTION(target, "Must have stream transport service");
+
+ // Here a Event to make everything async:
+ RefPtr<ArchiveReaderEvent> event;
+
+ /* FIXME: If we want to support more than 1 format we should check the content type here: */
+ event = new ArchiveReaderZipEvent(this, mEncoding);
+ rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // In order to be sure that this object exists when the event finishes its task,
+ // we increase the refcount here:
+ AddRef();
+
+ return NS_OK;
+}
+
+// Data received from the dispatched event:
+void
+ArchiveReader::Ready(nsTArray<RefPtr<File>>& aFileList,
+ nsresult aStatus)
+{
+ mStatus = READY;
+
+ // Let's store the values:
+ mData.fileList = aFileList;
+ mData.status = aStatus;
+
+ // Propagate the results:
+ for (uint32_t index = 0; index < mRequests.Length(); ++index) {
+ RefPtr<ArchiveRequest> request = mRequests[index];
+ RequestReady(request);
+ }
+
+ mRequests.Clear();
+
+ // The async operation is concluded, we can decrease the reference:
+ Release();
+}
+
+void
+ArchiveReader::RequestReady(ArchiveRequest* aRequest)
+{
+ // The request will do the rest:
+ aRequest->ReaderReady(mData.fileList, mData.status);
+}
+
+already_AddRefed<ArchiveRequest>
+ArchiveReader::GetFilenames()
+{
+ RefPtr<ArchiveRequest> request = GenerateArchiveRequest();
+ request->OpGetFilenames();
+
+ return request.forget();
+}
+
+already_AddRefed<ArchiveRequest>
+ArchiveReader::GetFile(const nsAString& filename)
+{
+ RefPtr<ArchiveRequest> request = GenerateArchiveRequest();
+ request->OpGetFile(filename);
+
+ return request.forget();
+}
+
+already_AddRefed<ArchiveRequest>
+ArchiveReader::GetFiles()
+{
+ RefPtr<ArchiveRequest> request = GenerateArchiveRequest();
+ request->OpGetFiles();
+
+ return request.forget();
+}
+
+already_AddRefed<ArchiveRequest>
+ArchiveReader::GenerateArchiveRequest()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ return ArchiveRequest::Create(mWindow, this);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ArchiveReader,
+ mBlobImpl,
+ mWindow,
+ mData.fileList,
+ mRequests)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ArchiveReader)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ArchiveReader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ArchiveReader)
diff --git a/dom/archivereader/ArchiveReader.h b/dom/archivereader/ArchiveReader.h
new file mode 100644
index 000000000..687392e56
--- /dev/null
+++ b/dom/archivereader/ArchiveReader.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_archivereader_domarchivereader_h__
+#define mozilla_dom_archivereader_domarchivereader_h__
+
+#include "nsWrapperCache.h"
+
+#include "ArchiveReaderCommon.h"
+
+#include "nsCOMArray.h"
+#include "nsIChannel.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace dom {
+struct ArchiveReaderOptions;
+class Blob;
+class BlobImpl;
+class File;
+class GlobalObject;
+} // namespace dom
+} // namespace mozilla
+
+BEGIN_ARCHIVEREADER_NAMESPACE
+
+class ArchiveRequest;
+
+/**
+ * This is the ArchiveReader object
+ */
+class ArchiveReader final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ArchiveReader)
+
+ static already_AddRefed<ArchiveReader>
+ Constructor(const GlobalObject& aGlobal, Blob& aBlob,
+ const ArchiveReaderOptions& aOptions, ErrorResult& aError);
+
+ ArchiveReader(Blob& aBlob, nsPIDOMWindowInner* aWindow,
+ const nsACString& aEncoding);
+
+ nsPIDOMWindowInner* GetParentObject() const
+ {
+ return mWindow;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<ArchiveRequest> GetFilenames();
+ already_AddRefed<ArchiveRequest> GetFile(const nsAString& filename);
+ already_AddRefed<ArchiveRequest> GetFiles();
+
+ nsresult GetInputStream(nsIInputStream** aInputStream);
+ nsresult GetSize(uint64_t* aSize);
+
+public: // for the ArchiveRequest:
+ nsresult RegisterRequest(ArchiveRequest* aRequest);
+
+public: // For events:
+ BlobImpl* GetBlobImpl() const
+ {
+ return mBlobImpl;
+ }
+
+ void Ready(nsTArray<RefPtr<File>>& aFileList, nsresult aStatus);
+
+private:
+ ~ArchiveReader();
+
+ already_AddRefed<ArchiveRequest> GenerateArchiveRequest();
+
+ nsresult OpenArchive();
+
+ void RequestReady(ArchiveRequest* aRequest);
+
+protected:
+ // The archive blob/file
+ RefPtr<BlobImpl> mBlobImpl;
+
+ // The window is needed by the requests
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ // Are we ready to return data?
+ enum {
+ NOT_STARTED = 0,
+ WORKING,
+ READY
+ } mStatus;
+
+ // State of the read:
+ enum {
+ Header, // We are collecting the header: 30bytes
+ Name, // The name length is contained in the header
+ Data, // The length of the data segment COULD be written in the header
+ Search // ... if the data length is unknown (== 0) we wait until we read a new header
+ } mReadStatus;
+
+ // List of requests to be processed
+ nsTArray<RefPtr<ArchiveRequest> > mRequests;
+
+ // Everything related to the blobs and the status:
+ struct {
+ nsTArray<RefPtr<File>> fileList;
+ nsresult status;
+ } mData;
+
+ nsCString mEncoding;
+};
+
+END_ARCHIVEREADER_NAMESPACE
+
+#endif // mozilla_dom_archivereader_domarchivereader_h__
diff --git a/dom/archivereader/ArchiveReaderCommon.h b/dom/archivereader/ArchiveReaderCommon.h
new file mode 100644
index 000000000..ab40c3bef
--- /dev/null
+++ b/dom/archivereader/ArchiveReaderCommon.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_archivereader_archivereader_h
+#define mozilla_dom_archivereader_archivereader_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDebug.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#define BEGIN_ARCHIVEREADER_NAMESPACE \
+ namespace mozilla { namespace dom { namespace archivereader {
+#define END_ARCHIVEREADER_NAMESPACE \
+ } /* namespace archivereader */ } /* namespace dom */ } /* namespace mozilla */
+#define USING_ARCHIVEREADER_NAMESPACE \
+ using namespace mozilla::dom::archivereader;
+
+#endif // mozilla_dom_archivereader_archivereadercommon_h
diff --git a/dom/archivereader/ArchiveRequest.cpp b/dom/archivereader/ArchiveRequest.cpp
new file mode 100644
index 000000000..ec1686804
--- /dev/null
+++ b/dom/archivereader/ArchiveRequest.cpp
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ArchiveRequest.h"
+
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/ArchiveRequestBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+USING_ARCHIVEREADER_NAMESPACE
+
+/**
+ * Class used to make asynchronous the ArchiveRequest.
+ */
+class ArchiveRequestEvent : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ explicit ArchiveRequestEvent(ArchiveRequest* aRequest)
+ : mRequest(aRequest)
+ {
+ MOZ_COUNT_CTOR(ArchiveRequestEvent);
+ }
+
+protected:
+ ~ArchiveRequestEvent()
+ {
+ MOZ_COUNT_DTOR(ArchiveRequestEvent);
+ }
+
+private: //data
+ RefPtr<ArchiveRequest> mRequest;
+};
+
+NS_IMETHODIMP
+ArchiveRequestEvent::Run()
+{
+ MOZ_ASSERT(mRequest, "the request is not longer valid");
+ mRequest->Run();
+ return NS_OK;
+}
+
+// ArchiveRequest
+
+ArchiveRequest::ArchiveRequest(nsPIDOMWindowInner* aWindow,
+ ArchiveReader* aReader)
+: DOMRequest(aWindow),
+ mArchiveReader(aReader)
+{
+ MOZ_ASSERT(aReader);
+
+ MOZ_COUNT_CTOR(ArchiveRequest);
+
+ /* An event to make this request asynchronous: */
+ RefPtr<ArchiveRequestEvent> event = new ArchiveRequestEvent(this);
+ NS_DispatchToCurrentThread(event);
+}
+
+ArchiveRequest::~ArchiveRequest()
+{
+ MOZ_COUNT_DTOR(ArchiveRequest);
+}
+
+nsresult
+ArchiveRequest::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mCanHandle = true;
+ aVisitor.mParentTarget = nullptr;
+ return NS_OK;
+}
+
+/* virtual */ JSObject*
+ArchiveRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ArchiveRequestBinding::Wrap(aCx, this, aGivenProto);
+}
+
+ArchiveReader*
+ArchiveRequest::Reader() const
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ return mArchiveReader;
+}
+
+// Here the request is processed:
+void
+ArchiveRequest::Run()
+{
+ // Register this request to the reader.
+ // When the reader is ready to return data, a 'Ready()' will be called
+ nsresult rv = mArchiveReader->RegisterRequest(this);
+ if (NS_FAILED(rv)) {
+ FireError(rv);
+ }
+}
+
+void
+ArchiveRequest::OpGetFilenames()
+{
+ mOperation = GetFilenames;
+}
+
+void
+ArchiveRequest::OpGetFile(const nsAString& aFilename)
+{
+ mOperation = GetFile;
+ mFilename = aFilename;
+}
+
+void
+ArchiveRequest::OpGetFiles()
+{
+ mOperation = GetFiles;
+}
+
+nsresult
+ArchiveRequest::ReaderReady(nsTArray<RefPtr<File>>& aFileList,
+ nsresult aStatus)
+{
+ if (NS_FAILED(aStatus)) {
+ FireError(aStatus);
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> result(cx);
+ switch (mOperation) {
+ case GetFilenames:
+ rv = GetFilenamesResult(cx, result.address(), aFileList);
+ break;
+
+ case GetFile:
+ rv = GetFileResult(cx, &result, aFileList);
+ break;
+
+ case GetFiles:
+ rv = GetFilesResult(cx, &result, aFileList);
+ break;
+
+ default:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Get*Result failed!");
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ FireSuccess(result);
+ }
+ else {
+ FireError(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ArchiveRequest::GetFilenamesResult(JSContext* aCx,
+ JS::Value* aValue,
+ nsTArray<RefPtr<File>>& aFileList)
+{
+ JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, aFileList.Length()));
+
+ if (!array) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ JS::Rooted<JSString*> str(aCx);
+ for (uint32_t i = 0; i < aFileList.Length(); ++i) {
+ RefPtr<File> file = aFileList[i];
+
+ nsString filename;
+ file->GetName(filename);
+
+ str = JS_NewUCStringCopyZ(aCx, filename.get());
+ NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
+
+ if (!JS_DefineElement(aCx, array, i, str, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (!JS_FreezeObject(aCx, array)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aValue->setObject(*array);
+ return NS_OK;
+}
+
+nsresult
+ArchiveRequest::GetFileResult(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ nsTArray<RefPtr<File>>& aFileList)
+{
+ for (uint32_t i = 0; i < aFileList.Length(); ++i) {
+ RefPtr<File> file = aFileList[i];
+
+ nsString filename;
+ file->GetName(filename);
+
+ if (filename == mFilename) {
+ if (!ToJSValue(aCx, file, aValue)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+ArchiveRequest::GetFilesResult(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ nsTArray<RefPtr<File>>& aFileList)
+{
+ JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, aFileList.Length()));
+ if (!array) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < aFileList.Length(); ++i) {
+ RefPtr<File> file = aFileList[i];
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, file, &value)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!JS_DefineElement(aCx, array, i, value, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ aValue.setObject(*array);
+ return NS_OK;
+}
+
+// static
+already_AddRefed<ArchiveRequest>
+ArchiveRequest::Create(nsPIDOMWindowInner* aOwner,
+ ArchiveReader* aReader)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ RefPtr<ArchiveRequest> request = new ArchiveRequest(aOwner, aReader);
+
+ return request.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ArchiveRequest, DOMRequest,
+ mArchiveReader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ArchiveRequest)
+NS_INTERFACE_MAP_END_INHERITING(DOMRequest)
+
+NS_IMPL_ADDREF_INHERITED(ArchiveRequest, DOMRequest)
+NS_IMPL_RELEASE_INHERITED(ArchiveRequest, DOMRequest)
diff --git a/dom/archivereader/ArchiveRequest.h b/dom/archivereader/ArchiveRequest.h
new file mode 100644
index 000000000..3988f1aa1
--- /dev/null
+++ b/dom/archivereader/ArchiveRequest.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_archivereader_domarchiverequest_h__
+#define mozilla_dom_archivereader_domarchiverequest_h__
+
+#include "mozilla/Attributes.h"
+#include "ArchiveReader.h"
+#include "DOMRequest.h"
+
+#include "ArchiveReaderCommon.h"
+
+namespace mozilla {
+class EventChainPreVisitor;
+} // namespace mozilla
+
+BEGIN_ARCHIVEREADER_NAMESPACE
+
+/**
+ * This is the ArchiveRequest that handles any operation
+ * related to ArchiveReader
+ */
+class ArchiveRequest : public mozilla::dom::DOMRequest
+{
+public:
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ ArchiveReader* Reader() const;
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ArchiveRequest, DOMRequest)
+
+ ArchiveRequest(nsPIDOMWindowInner* aWindow,
+ ArchiveReader* aReader);
+
+ // nsIDOMEventTarget
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+
+public:
+ // This is called by the DOMArchiveRequestEvent
+ void Run();
+
+ // Set the types for this request
+ void OpGetFilenames();
+ void OpGetFile(const nsAString& aFilename);
+ void OpGetFiles();
+
+ nsresult ReaderReady(nsTArray<RefPtr<File>>& aFileList, nsresult aStatus);
+
+public: // static
+ static already_AddRefed<ArchiveRequest> Create(nsPIDOMWindowInner* aOwner,
+ ArchiveReader* aReader);
+
+private:
+ ~ArchiveRequest();
+
+ nsresult GetFilenamesResult(JSContext* aCx,
+ JS::Value* aValue,
+ nsTArray<RefPtr<File>>& aFileList);
+ nsresult GetFileResult(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ nsTArray<RefPtr<File>>& aFileList);
+ nsresult GetFilesResult(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ nsTArray<RefPtr<File>>& aFileList);
+
+protected:
+ // The reader:
+ RefPtr<ArchiveReader> mArchiveReader;
+
+ // The operation:
+ enum {
+ GetFilenames,
+ GetFile,
+ GetFiles
+ } mOperation;
+
+ // The filename (needed by GetFile):
+ nsString mFilename;
+};
+
+END_ARCHIVEREADER_NAMESPACE
+
+#endif // mozilla_dom_archivereader_domarchiverequest_h__
diff --git a/dom/archivereader/ArchiveZipEvent.cpp b/dom/archivereader/ArchiveZipEvent.cpp
new file mode 100644
index 000000000..56251eef6
--- /dev/null
+++ b/dom/archivereader/ArchiveZipEvent.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ArchiveZipEvent.h"
+#include "ArchiveZipFile.h"
+
+#include "nsContentUtils.h"
+#include "nsCExternalHandlerService.h"
+
+#include "mozilla/UniquePtr.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+USING_ARCHIVEREADER_NAMESPACE
+
+#ifndef PATH_MAX
+# define PATH_MAX 65536 // The filename length is stored in 2 bytes
+#endif
+
+ArchiveZipItem::ArchiveZipItem(const char* aFilename,
+ const ZipCentral& aCentralStruct,
+ const nsACString& aEncoding)
+: mFilename(aFilename),
+ mCentralStruct(aCentralStruct),
+ mEncoding(aEncoding)
+{
+ MOZ_COUNT_CTOR(ArchiveZipItem);
+}
+
+ArchiveZipItem::~ArchiveZipItem()
+{
+ MOZ_COUNT_DTOR(ArchiveZipItem);
+}
+
+nsresult
+ArchiveZipItem::ConvertFilename()
+{
+ if (mEncoding.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString filenameU;
+ nsresult rv = nsContentUtils::ConvertStringFromEncoding(
+ mEncoding,
+ mFilename, filenameU);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (filenameU.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mFilenameU = filenameU;
+ return NS_OK;
+}
+
+nsresult
+ArchiveZipItem::GetFilename(nsString& aFilename)
+{
+ if (mFilenameU.IsEmpty()) {
+ // Maybe this string is UTF-8:
+ if (IsUTF8(mFilename, false)) {
+ mFilenameU = NS_ConvertUTF8toUTF16(mFilename);
+ }
+
+ // Let's use the enconding value for the dictionary
+ else {
+ nsresult rv = ConvertFilename();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ aFilename = mFilenameU;
+ return NS_OK;
+}
+
+// From zipItem to File:
+already_AddRefed<File>
+ArchiveZipItem::GetFile(ArchiveReader* aArchiveReader)
+{
+ nsString filename;
+
+ if (NS_FAILED(GetFilename(filename))) {
+ return nullptr;
+ }
+
+ RefPtr<dom::File> file = dom::File::Create(aArchiveReader,
+ new ArchiveZipBlobImpl(filename,
+ NS_ConvertUTF8toUTF16(GetType()),
+ StrToInt32(mCentralStruct.orglen),
+ mCentralStruct, aArchiveReader->GetBlobImpl()));
+ MOZ_ASSERT(file);
+ return file.forget();
+}
+
+uint32_t
+ArchiveZipItem::StrToInt32(const uint8_t* aStr)
+{
+ return (uint32_t)( (aStr [0] << 0) |
+ (aStr [1] << 8) |
+ (aStr [2] << 16) |
+ (aStr [3] << 24) );
+}
+
+uint16_t
+ArchiveZipItem::StrToInt16(const uint8_t* aStr)
+{
+ return (uint16_t) ((aStr [0]) | (aStr [1] << 8));
+}
+
+// ArchiveReaderZipEvent
+
+ArchiveReaderZipEvent::ArchiveReaderZipEvent(ArchiveReader* aArchiveReader,
+ const nsACString& aEncoding)
+: ArchiveReaderEvent(aArchiveReader),
+ mEncoding(aEncoding)
+{
+}
+
+// NOTE: this runs in a different thread!!
+nsresult
+ArchiveReaderZipEvent::Exec()
+{
+ uint32_t centralOffset(0);
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = mArchiveReader->GetInputStream(getter_AddRefs(inputStream));
+ if (NS_FAILED(rv) || !inputStream) {
+ return RunShare(NS_ERROR_UNEXPECTED);
+ }
+
+ // From the input stream to a seekable stream
+ nsCOMPtr<nsISeekableStream> seekableStream;
+ seekableStream = do_QueryInterface(inputStream);
+ if (!seekableStream) {
+ return RunShare(NS_ERROR_UNEXPECTED);
+ }
+
+ uint64_t size;
+ rv = mArchiveReader->GetSize(&size);
+ if (NS_FAILED(rv)) {
+ return RunShare(NS_ERROR_UNEXPECTED);
+ }
+
+ // Reading backward.. looking for the ZipEnd signature
+ for (uint64_t curr = size - ZIPEND_SIZE; curr > 4; --curr) {
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, curr);
+
+ uint8_t buffer[ZIPEND_SIZE];
+ uint32_t ret;
+
+ rv = inputStream->Read((char*)buffer, sizeof(buffer), &ret);
+ if (NS_FAILED(rv) || ret != sizeof(buffer)) {
+ return RunShare(NS_ERROR_UNEXPECTED);
+ }
+
+ // Here we are:
+ if (ArchiveZipItem::StrToInt32(buffer) == ENDSIG) {
+ centralOffset = ArchiveZipItem::StrToInt32(((ZipEnd*)buffer)->offset_central_dir);
+ break;
+ }
+ }
+
+ // No central Offset
+ if (!centralOffset || centralOffset >= size - ZIPEND_SIZE) {
+ return RunShare(NS_ERROR_FAILURE);
+ }
+
+ // Seek to the first central directory:
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, centralOffset);
+
+ // For each central directory:
+ while (centralOffset <= size - ZIPCENTRAL_SIZE) {
+ ZipCentral centralStruct;
+ uint32_t ret;
+
+ rv = inputStream->Read((char*)&centralStruct, ZIPCENTRAL_SIZE, &ret);
+ if (NS_FAILED(rv) || ret != ZIPCENTRAL_SIZE) {
+ return RunShare(NS_ERROR_UNEXPECTED);
+ }
+
+ uint16_t filenameLen = ArchiveZipItem::StrToInt16(centralStruct.filename_len);
+ uint16_t extraLen = ArchiveZipItem::StrToInt16(centralStruct.extrafield_len);
+ uint16_t commentLen = ArchiveZipItem::StrToInt16(centralStruct.commentfield_len);
+
+ // Point to the next item at the top of loop
+ centralOffset += ZIPCENTRAL_SIZE + filenameLen + extraLen + commentLen;
+ if (filenameLen == 0 || filenameLen >= PATH_MAX || centralOffset >= size) {
+ return RunShare(NS_ERROR_FILE_CORRUPTED);
+ }
+
+ // Read the name:
+ auto filename = MakeUnique<char[]>(filenameLen + 1);
+ rv = inputStream->Read(filename.get(), filenameLen, &ret);
+ if (NS_FAILED(rv) || ret != filenameLen) {
+ return RunShare(NS_ERROR_UNEXPECTED);
+ }
+
+ filename[filenameLen] = 0;
+
+ // We ignore the directories:
+ if (filename[filenameLen - 1] != '/') {
+ mFileList.AppendElement(new ArchiveZipItem(filename.get(), centralStruct,
+ mEncoding));
+ }
+
+ // Ignore the rest
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, extraLen + commentLen);
+ }
+
+ return RunShare(NS_OK);
+}
diff --git a/dom/archivereader/ArchiveZipEvent.h b/dom/archivereader/ArchiveZipEvent.h
new file mode 100644
index 000000000..2c25740f3
--- /dev/null
+++ b/dom/archivereader/ArchiveZipEvent.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_archivereader_domarchivezipevent_h__
+#define mozilla_dom_archivereader_domarchivezipevent_h__
+
+#include "mozilla/Attributes.h"
+#include "ArchiveEvent.h"
+
+#include "ArchiveReaderCommon.h"
+#include "zipstruct.h"
+
+BEGIN_ARCHIVEREADER_NAMESPACE
+
+/**
+ * ArchiveZipItem - ArchiveItem for ArchiveReaderZipEvent
+ */
+class ArchiveZipItem : public ArchiveItem
+{
+public:
+ ArchiveZipItem(const char* aFilename,
+ const ZipCentral& aCentralStruct,
+ const nsACString& aEncoding);
+protected:
+ virtual ~ArchiveZipItem();
+
+public:
+ nsresult GetFilename(nsString& aFilename) override;
+
+ // From zipItem to File:
+ virtual already_AddRefed<File>
+ GetFile(ArchiveReader* aArchiveReader) override;
+
+public: // for the event
+ static uint32_t StrToInt32(const uint8_t* aStr);
+ static uint16_t StrToInt16(const uint8_t* aStr);
+
+private:
+ nsresult ConvertFilename();
+
+private: // data
+ nsCString mFilename;
+
+ nsString mFilenameU;
+ ZipCentral mCentralStruct;
+
+ nsCString mEncoding;
+};
+
+/**
+ * ArchiveReaderEvent implements the ArchiveReaderEvent for the ZIP format
+ */
+class ArchiveReaderZipEvent : public ArchiveReaderEvent
+{
+public:
+ ArchiveReaderZipEvent(ArchiveReader* aArchiveReader,
+ const nsACString& aEncoding);
+
+ nsresult Exec() override;
+
+private:
+ nsCString mEncoding;
+};
+
+END_ARCHIVEREADER_NAMESPACE
+
+#endif // mozilla_dom_archivereader_domarchivezipevent_h__
diff --git a/dom/archivereader/ArchiveZipFile.cpp b/dom/archivereader/ArchiveZipFile.cpp
new file mode 100644
index 000000000..d374fe91f
--- /dev/null
+++ b/dom/archivereader/ArchiveZipFile.cpp
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ArchiveZipFile.h"
+#include "ArchiveZipEvent.h"
+
+#include "nsIInputStream.h"
+#include "zlib.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/File.h"
+
+using namespace mozilla::dom;
+USING_ARCHIVEREADER_NAMESPACE
+
+#define ZIP_CHUNK 16384
+
+/**
+ * Input stream object for zip files
+ */
+class ArchiveInputStream final : public nsIInputStream,
+ public nsISeekableStream
+{
+public:
+ ArchiveInputStream(uint64_t aParentSize,
+ nsIInputStream* aInputStream,
+ nsString& aFilename,
+ uint32_t aStart,
+ uint32_t aLength,
+ ZipCentral& aCentral)
+ : mCentral(aCentral),
+ mFilename(aFilename),
+ mStart(aStart),
+ mLength(aLength),
+ mStatus(NotStarted)
+ {
+ MOZ_COUNT_CTOR(ArchiveInputStream);
+
+ // Reset the data:
+ memset(&mData, 0, sizeof(mData));
+
+ mData.parentSize = aParentSize;
+ mData.inputStream = aInputStream;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+
+private:
+ virtual ~ArchiveInputStream()
+ {
+ MOZ_COUNT_DTOR(ArchiveInputStream);
+ Close();
+ }
+
+ nsresult Init();
+
+private: // data
+ ZipCentral mCentral;
+ nsString mFilename;
+ uint32_t mStart;
+ uint32_t mLength;
+
+ z_stream mZs;
+
+ enum {
+ NotStarted,
+ Started,
+ Done
+ } mStatus;
+
+ struct {
+ uint64_t parentSize;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ unsigned char input[ZIP_CHUNK];
+ uint32_t sizeToBeRead;
+ uint32_t cursor;
+
+ bool compressed; // a zip file can contain stored or compressed files
+ } mData;
+};
+
+NS_IMPL_ISUPPORTS(ArchiveInputStream,
+ nsIInputStream,
+ nsISeekableStream)
+
+nsresult
+ArchiveInputStream::Init()
+{
+ nsresult rv;
+
+ memset(&mZs, 0, sizeof(z_stream));
+ int zerr = inflateInit2(&mZs, -MAX_WBITS);
+ if (zerr != Z_OK) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mData.sizeToBeRead = ArchiveZipItem::StrToInt32(mCentral.size);
+
+ uint32_t offset = ArchiveZipItem::StrToInt32(mCentral.localhdr_offset);
+
+ // The file is corrupt
+ if (mData.parentSize < ZIPLOCAL_SIZE ||
+ offset > mData.parentSize - ZIPLOCAL_SIZE) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // From the input stream to a seekable stream
+ nsCOMPtr<nsISeekableStream> seekableStream;
+ seekableStream = do_QueryInterface(mData.inputStream);
+ if (!seekableStream) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Seek + read the ZipLocal struct
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+ uint8_t buffer[ZIPLOCAL_SIZE];
+ uint32_t ret;
+
+ rv = mData.inputStream->Read((char*)buffer, ZIPLOCAL_SIZE, &ret);
+ if (NS_FAILED(rv) || ret != ZIPLOCAL_SIZE) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Signature check:
+ if (ArchiveZipItem::StrToInt32(buffer) != LOCALSIG) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ZipLocal local;
+ memcpy(&local, buffer, ZIPLOCAL_SIZE);
+
+ // Seek to the real data:
+ offset += ZIPLOCAL_SIZE +
+ ArchiveZipItem::StrToInt16(local.filename_len) +
+ ArchiveZipItem::StrToInt16(local.extrafield_len);
+
+ // The file is corrupt if there is not enough data
+ if (mData.parentSize < mData.sizeToBeRead ||
+ offset > mData.parentSize - mData.sizeToBeRead) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Data starts here:
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+
+ // The file is compressed or not?
+ mData.compressed = (ArchiveZipItem::StrToInt16(mCentral.method) != 0);
+
+ // We have to skip the first mStart bytes:
+ if (mStart != 0) {
+ rv = Seek(NS_SEEK_SET, mStart);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::Close()
+{
+ if (mStatus != NotStarted) {
+ inflateEnd(&mZs);
+ mStatus = NotStarted;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::Available(uint64_t* _retval)
+{
+ *_retval = mLength - mData.cursor - mStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::Read(char* aBuffer,
+ uint32_t aCount,
+ uint32_t* _retval)
+{
+ NS_ENSURE_ARG_POINTER(aBuffer);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+
+ // This is the first time:
+ if (mStatus == NotStarted) {
+ mStatus = Started;
+
+ rv = Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Let's set avail_out to -1 so we read something from the stream.
+ mZs.avail_out = (uInt)-1;
+ }
+
+ // Nothing more can be read
+ if (mStatus == Done) {
+ *_retval = 0;
+ return NS_OK;
+ }
+
+ // Stored file:
+ if (!mData.compressed) {
+ rv = mData.inputStream->Read(aBuffer,
+ (mData.sizeToBeRead > aCount ?
+ aCount : mData.sizeToBeRead),
+ _retval);
+ if (NS_SUCCEEDED(rv)) {
+ mData.sizeToBeRead -= *_retval;
+ mData.cursor += *_retval;
+
+ if (mData.sizeToBeRead == 0) {
+ mStatus = Done;
+ }
+ }
+
+ return rv;
+ }
+
+ // We have nothing ready to be processed:
+ if (mZs.avail_out != 0 && mData.sizeToBeRead != 0) {
+ uint32_t ret;
+ rv = mData.inputStream->Read((char*)mData.input,
+ (mData.sizeToBeRead > sizeof(mData.input) ?
+ sizeof(mData.input) : mData.sizeToBeRead),
+ &ret);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Terminator:
+ if (ret == 0) {
+ *_retval = 0;
+ return NS_OK;
+ }
+
+ mData.sizeToBeRead -= ret;
+ mZs.avail_in = ret;
+ mZs.next_in = mData.input;
+ }
+
+ mZs.avail_out = aCount;
+ mZs.next_out = (unsigned char*)aBuffer;
+
+ int ret = inflate(&mZs, mData.sizeToBeRead ? Z_NO_FLUSH : Z_FINISH);
+ if (ret != Z_BUF_ERROR && ret != Z_OK && ret != Z_STREAM_END) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (ret == Z_STREAM_END) {
+ mStatus = Done;
+ }
+
+ *_retval = aCount - mZs.avail_out;
+ mData.cursor += *_retval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* _retval)
+{
+ // don't have a buffer to read from, so this better not be called!
+ NS_NOTREACHED("Consumers should be using Read()!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::IsNonBlocking(bool* _retval)
+{
+ // We are blocking
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ int64_t pos = aOffset;
+
+ switch (aWhence) {
+ case NS_SEEK_SET:
+ break;
+
+ case NS_SEEK_CUR:
+ pos += mData.cursor;
+ break;
+
+ case NS_SEEK_END:
+ pos += mLength;
+ break;
+
+ default:
+ NS_NOTREACHED("unexpected whence value");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (pos == int64_t(mData.cursor)) {
+ return NS_OK;
+ }
+
+ if (pos < 0 || pos >= mLength) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We have to terminate the previous operation:
+ nsresult rv;
+ if (mStatus != NotStarted) {
+ rv = Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Reset the cursor:
+ mData.cursor = 0;
+
+ // Note: This code is heavy but inflate does not have any seek() support:
+ uint32_t ret;
+ char buffer[1024];
+ while (pos > 0) {
+ rv = Read(buffer, pos > int64_t(sizeof(buffer)) ? sizeof(buffer) : pos, &ret);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (ret == 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ pos -= ret;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::Tell(int64_t *aResult)
+{
+ *aResult = mData.cursor;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::SetEOF()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// ArchiveZipBlobImpl
+
+void
+ArchiveZipBlobImpl::GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv)
+{
+ if (mLength > INT32_MAX) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ uint64_t size = mBlobImpl->GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ mBlobImpl->GetInternalStream(getter_AddRefs(inputStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ RefPtr<ArchiveInputStream> stream = new ArchiveInputStream(size,
+ inputStream,
+ mFilename,
+ mStart,
+ mLength,
+ mCentral);
+
+ stream.forget(aStream);
+}
+
+already_AddRefed<mozilla::dom::BlobImpl>
+ArchiveZipBlobImpl::CreateSlice(uint64_t aStart,
+ uint64_t aLength,
+ const nsAString& aContentType,
+ mozilla::ErrorResult& aRv)
+{
+ RefPtr<BlobImpl> impl =
+ new ArchiveZipBlobImpl(mFilename, mContentType, aStart, mLength, mCentral,
+ mBlobImpl);
+ return impl.forget();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ArchiveZipBlobImpl, BlobImpl)
diff --git a/dom/archivereader/ArchiveZipFile.h b/dom/archivereader/ArchiveZipFile.h
new file mode 100644
index 000000000..d1196486c
--- /dev/null
+++ b/dom/archivereader/ArchiveZipFile.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_archivereader_domarchivefile_h__
+#define mozilla_dom_archivereader_domarchivefile_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/File.h"
+
+#include "ArchiveReader.h"
+
+#include "ArchiveReaderCommon.h"
+#include "zipstruct.h"
+
+BEGIN_ARCHIVEREADER_NAMESPACE
+
+/**
+ * ArchiveZipBlobImpl to BlobImpl
+ */
+class ArchiveZipBlobImpl : public BlobImplBase
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ ArchiveZipBlobImpl(const nsAString& aName,
+ const nsAString& aContentType,
+ uint64_t aLength,
+ ZipCentral& aCentral,
+ BlobImpl* aBlobImpl)
+ : BlobImplBase(aName, aContentType, aLength),
+ mCentral(aCentral),
+ mBlobImpl(aBlobImpl),
+ mFilename(aName)
+ {
+ MOZ_ASSERT(mBlobImpl);
+ MOZ_COUNT_CTOR(ArchiveZipBlobImpl);
+ }
+
+ ArchiveZipBlobImpl(const nsAString& aName,
+ const nsAString& aContentType,
+ uint64_t aStart,
+ uint64_t aLength,
+ ZipCentral& aCentral,
+ BlobImpl* aBlobImpl)
+ : BlobImplBase(aContentType, aStart, aLength),
+ mCentral(aCentral),
+ mBlobImpl(aBlobImpl),
+ mFilename(aName)
+ {
+ MOZ_ASSERT(mBlobImpl);
+ MOZ_COUNT_CTOR(ArchiveZipBlobImpl);
+ }
+
+ // Overrides:
+ virtual void GetInternalStream(nsIInputStream** aInputStream,
+ ErrorResult& aRv) override;
+
+protected:
+ virtual ~ArchiveZipBlobImpl()
+ {
+ MOZ_COUNT_DTOR(ArchiveZipBlobImpl);
+ }
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType,
+ mozilla::ErrorResult& aRv) override;
+
+private: // Data
+ ZipCentral mCentral;
+ RefPtr<BlobImpl> mBlobImpl;
+
+ nsString mFilename;
+};
+
+END_ARCHIVEREADER_NAMESPACE
+
+#endif // mozilla_dom_archivereader_domarchivefile_h__
diff --git a/dom/archivereader/moz.build b/dom/archivereader/moz.build
new file mode 100644
index 000000000..57dbfa121
--- /dev/null
+++ b/dom/archivereader/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom.archivereader += [
+ 'ArchiveEvent.h',
+ 'ArchiveReader.h',
+ 'ArchiveReaderCommon.h',
+ 'ArchiveRequest.h',
+ 'ArchiveZipEvent.h',
+ 'ArchiveZipFile.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ArchiveEvent.cpp',
+ 'ArchiveReader.cpp',
+ 'ArchiveRequest.cpp',
+ 'ArchiveZipEvent.cpp',
+ 'ArchiveZipFile.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '../base',
+]
+
+FINAL_LIBRARY = 'xul'
+
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
diff --git a/dom/archivereader/test/helpers.js b/dom/archivereader/test/helpers.js
new file mode 100644
index 000000000..3ba430436
--- /dev/null
+++ b/dom/archivereader/test/helpers.js
@@ -0,0 +1,31 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator;
+
+function runTest()
+{
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({'set': [ ["dom.archivereader.enabled", true] ]}, function() {
+ SpecialPowers.createFiles(filesToCreate(),
+ function (files) {
+ testGenerator = testSteps(files);
+ return testGenerator.next();
+ },
+ function (msg) {
+ ok(false, "File creation error: " + msg);
+ finishTest();
+ });
+ });
+}
+
+function finishTest()
+{
+ SpecialPowers.popPrefEnv(function() {
+ testGenerator.close();
+ SimpleTest.finish();
+ });
+}
diff --git a/dom/archivereader/test/mochitest.ini b/dom/archivereader/test/mochitest.ini
new file mode 100644
index 000000000..6cf9e9c43
--- /dev/null
+++ b/dom/archivereader/test/mochitest.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ helpers.js
+
+[test_basic.html]
+[test_nonUnicode.html]
+[test_zip_in_zip.html]
diff --git a/dom/archivereader/test/test_basic.html b/dom/archivereader/test/test_basic.html
new file mode 100644
index 000000000..b3e5ab852
--- /dev/null
+++ b/dom/archivereader/test/test_basic.html
@@ -0,0 +1,227 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Archive Reader Test</title>
+
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script type="text/javascript;version=1.7">
+ function filesToCreate() {
+ var binaryString = '504B03040A00000000002E6BF14000000000000000000000000005001C00746573742F555409000337CA055039CA055075780B' +
+ '000104E803000004E8030000504B03041400000008002D6BF1401780E15015000000580200000A001C00746573742F612E7478' +
+ '74555409000336CA05503ACA055075780B000104E803000004E8030000CB48CDC9C95728CF2FCA49E1CA18658FB2A9C4060050' +
+ '4B03040A00000000002F88EC40662E847010000000100000000A001C00746573742F622E74787455540900035A65FF4F42C505' +
+ '5075780B000104E803000004E803000068656C6C6F20776F726C642C2032210A504B01021E030A00000000002E6BF140000000' +
+ '000000000000000000050018000000000000001000FD4100000000746573742F555405000337CA055075780B000104E8030000' +
+ '04E8030000504B01021E031400000008002D6BF1401780E15015000000580200000A0018000000000001000000B4813F000000' +
+ '746573742F612E747874555405000336CA055075780B000104E803000004E8030000504B01021E030A00000000002F88EC4066' +
+ '2E847010000000100000000A0018000000000001000000B48198000000746573742F622E74787455540500035A65FF4F75780B' +
+ '000104E803000004E8030000504B05060000000003000300EB000000EC0000000000';
+
+ var binaryData = "";
+ for (var i = 0, len = binaryString.length / 2; i < len; ++i) {
+ var hex = binaryString[i * 2] + binaryString[i * 2 + 1];
+ binaryData += String.fromCharCode(parseInt(hex,16));
+ }
+
+ return [ {name: "fileArchiveReader.zip", data: binaryData},
+ {name: "fileArchiveReader.txt", data: "Hello World"}];
+ }
+
+ handleFinished = 0;
+ function markTestDone() {
+ ++handleFinished;
+ if (isFinished()) {
+ finishTest();
+ }
+ }
+ function isFinished() {
+ return handleFinished == 6;
+ }
+
+ function testSteps(files)
+ {
+ var binaryFile = files[0];
+ var textFile = files[1];
+
+ var status;
+
+ // Create - wrong 1
+ try {
+ var r = new ArchiveReader();
+ status = false;
+ }
+ catch(e) {
+ status = true;
+ }
+ ok(status, "ArchiveReader() without args MUST fail");
+
+ // Create - wrong 2
+ try {
+ var r = new ArchiveReader(true);
+ status = false;
+ }
+ catch(e) {
+ status = true;
+ }
+ ok(status, "ArchiveReader() without a blob arg MUST fail");
+
+ // Create - wrong 3
+ try {
+ var r = new ArchiveReader("hello world");
+ status = false;
+ }
+ catch(e) {
+ status = true;
+ }
+ ok(status, "ArchiveReader() without a blob arg MUST fail");
+
+ // Create - good! (but with a text file)
+ var rt = new ArchiveReader(textFile);
+ isnot(rt, null, "ArchiveReader cannot be null");
+
+ // GetFilename
+ var handle = rt.getFilenames();
+ isnot(handle, null, "ArchiveReader.getFilenames() cannot be null");
+ handle.onsuccess = function() {
+ ok(false, "ArchiveReader.getFilenames() should return a 'failure' if the input file is not a zip");
+ markTestDone();
+ }
+ handle.onerror = function() {
+ ok(true, "ArchiveReader.getFilenames() should return a 'error' if the input file is a zip file");
+ is(this.reader, rt, "ArchiveRequest.reader == ArchiveReader");
+ markTestDone();
+ }
+
+ // Create - good!
+ var r = new ArchiveReader(binaryFile);
+ isnot(r, null, "ArchiveReader cannot be null");
+
+ // GetFilename
+ handle = r.getFilenames();
+ isnot(handle, null, "ArchiveReader.getFilenames() cannot be null");
+ handle.onsuccess = function() {
+ ok(true, "ArchiveReader.getFilenames() should return a 'success'");
+ is(this.result instanceof Array, true, "ArchiveReader.getFilenames() should return an array");
+ is(this.result.length, 2, "ArchiveReader.getFilenames(): the array contains 2 items");
+ is(this.result[0], "test/a.txt", "ArchiveReader.getFilenames(): first file is 'test/a.txt'");
+ is(this.result[1], "test/b.txt", "ArchiveReader.getFilenames(): second file is 'test/b.txt'");
+ ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+ markTestDone();
+ }
+ handle.onerror = function() {
+ ok(false, "ArchiveReader.getFilenames() should not return any 'error'");
+ markTestDone();
+ }
+
+ // GetFile - wrong (no args)
+ try {
+ r.getFile();
+ status = false;
+ }
+ catch(e) {
+ status = true;
+ }
+ ok(status, "ArchiveReader.getFile() without args fail");
+
+ var handle2 = r.getFile("hello world");
+ isnot(handle2, null, "ArchiveReader.getFile() cannot be null");
+ handle2.onsuccess = function() {
+ ok(false, "ArchiveReader.getFile('unknown file') should not return a 'success'");
+ markTestDone();
+ }
+ handle2.onerror = function() {
+ ok(true, "ArchiveReader.getFile('unknown file') should return an 'error'");
+ ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+ markTestDone();
+ }
+
+ var handle3 = r.getFile("test/b.txt");
+ isnot(handle3, null, "ArchiveReader.getFile() cannot be null");
+ handle3.onsuccess = function() {
+ ok(true, "ArchiveReader.getFile('test/b.txt') should return a 'success'");
+ ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+ is(this.result.name, "test/b.txt", "ArchiveReader.getFile('test/b.txt') the name MUST be 'test/b.txt'");
+ is(this.result.type, "text/plain", "ArchiveReader.getFile('test/b.txt') the type MUST be 'text/plain'");
+
+ var fr = new FileReader();
+ fr.readAsText(this.result);
+ fr.onerror = function() {
+ ok(false, "ArchiveReader + FileReader should work!");
+ markTestDone();
+ }
+ fr.onload = function(event) {
+ is(event.target.result, "hello world, 2!\n", "ArchiveReader + FileReader are working together.");
+ markTestDone();
+ }
+ }
+
+ handle3.onerror = function() {
+ ok(false, "ArchiveReader.getFile('test/b.txt') should not return an 'error'");
+ markTestDone();
+ }
+
+ var handle4 = r.getFile("test/a.txt");
+ isnot(handle4, null, "ArchiveReader.getFile() cannot be null");
+ handle4.onsuccess = function() {
+ ok(true, "ArchiveReader.getFile('test/a.txt') should return a 'success'");
+ ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+ is(this.result.name, "test/a.txt", "ArchiveReader.getFile('test/a.txt') the name MUST be 'test/a.txt'");
+ is(this.result.type, "text/plain", "ArchiveReader.getFile('test/a.txt') the type MUST be 'text/plain'");
+
+ var fr = new FileReader();
+ fr.readAsText(this.result);
+ fr.onerror = function() {
+ ok(false, "ArchiveReader + FileReader should work!");
+ markTestDone();
+ }
+ fr.onload = function(event) {
+ is(event.target.result.length, 600, "ArchiveReader + FileReader are working with a compress data");
+ var p = event.target.result.trim().split('\n');
+ is(p.length, 50, "ArchiveReader + FileReader are working with a compress data");
+
+ for (var i = 0; i < p.length; ++i)
+ is(p[i], "hello world", "ArchiveReader + FileReader are working with a compress data");
+ markTestDone();
+ }
+ }
+ handle4.onerror = function() {
+ ok(false, "ArchiveReader.getFile('test/a.txt') should not return an 'error'");
+ markTestDone();
+ }
+
+ var handle5 = r.getFiles();
+ isnot(handle5, null, "ArchiveReader.getFiles() cannot be null");
+ handle5.onsuccess = function() {
+ ok(true, "ArchiveReader.getFiles() should return a 'success'");
+ ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+
+ is(this.result.length, 2, "ArchiveReader.getFilenames(): the array contains 2 items");
+ is(this.result[0].name, "test/a.txt", "ArchiveReader.getFilenames(): first file is 'test/a.txt'");
+ is(this.result[1].name, "test/b.txt", "ArchiveReader.getFilenames(): second file is 'test/b.txt'");
+ is(this.result[0].type, "text/plain", "ArchiveReader.getFile('test/a.txt') the type MUST be 'text/plain'");
+ is(this.result[1].type, "text/plain", "ArchiveReader.getFile('test/a.txt') the type MUST be 'text/plain'");
+
+ markTestDone();
+ }
+ handle5.onerror = function() {
+ ok(false, "ArchiveReader.getFiles() should not return an 'error'");
+ markTestDone();
+ }
+ yield undefined;
+ }
+
+ </script>
+ <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();">
+<p id="display">
+</p>
+</body>
+
+</html>
diff --git a/dom/archivereader/test/test_nonUnicode.html b/dom/archivereader/test/test_nonUnicode.html
new file mode 100644
index 000000000..6237ce10f
--- /dev/null
+++ b/dom/archivereader/test/test_nonUnicode.html
@@ -0,0 +1,77 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Archive Reader Non-Unicode Test</title>
+
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script type="text/javascript;version=1.7">
+ function filesToCreate() {
+ var binaryData = "";
+ for (var i = 0, len = binaryString.length / 2; i < len; ++i) {
+ var hex = binaryString[i * 2] + binaryString[i * 2 + 1];
+ binaryData += String.fromCharCode(parseInt(hex,16));
+ }
+ return [ {name: "fileArchiveReader_nonUnicode.zip", data: binaryData} ];
+ }
+
+ function test1(binaryFile)
+ {
+ var r = new ArchiveReader(binaryFile, { encoding: "ISO-8859-1" });
+ isnot(r, null, "ArchiveReader cannot be null");
+
+ // GetFilename
+ var handle = r.getFilenames();
+ isnot(handle, null, "ArchiveReader.getFilenames() cannot be null");
+ handle.onsuccess = function() {
+ ok(true, "ArchiveReader.getFilenames() should return a 'success'");
+ is(this.result instanceof Array, true, "ArchiveReader.getFilenames() should return an array");
+ is(this.result.length, 1, "ArchiveReader.getFilenames(): the array contains 1 item");
+ ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+ dump('Content: ' + this.result[0] + '\n');
+ test2(binaryFile);
+ }
+ }
+
+ function test2(binaryFile)
+ {
+ try {
+ new ArchiveReader(binaryFile, { encoding: "random stuff" });
+ ok(false, "Should have thrown for bogus encoding label.");
+ } catch (e) {
+ ok(e instanceof RangeError, "Expected a RangeError");
+ }
+ finishTest();
+ }
+
+ function testSteps(files)
+ {
+ test1(files[0]);
+ yield undefined;
+ }
+
+
+ </script>
+ <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();">
+<p id="display">
+</p>
+<script type="text/javascript;version=1.7">
+var binaryString = '' +
+'504B0304140000000000255D094100000000000000000000000002000000912F504B03040A0000000000285D09416BB50A5A' +
+'010000000100000007000000912F9B2E747874D8504B01023F00140000000000255D09410000000000000000000000000200' +
+'24000000000000001000000000000000912F0A002000000000000100180062505F1A1376CD0162505F1A1376CD01FE3F8D59' +
+'1176CD01504B01023F000A0000000000285D09416BB50A5A0100000001000000070024000000000000002000000020000000' +
+'912F9B2E7478740A0020000000000001001800565EF41D1376CD0107BD73631176CD0107BD73631176CD01504B0506000000' +
+'0002000200AD000000460000000000';
+</script>
+</body>
+
+</html>
diff --git a/dom/archivereader/test/test_zip_in_zip.html b/dom/archivereader/test/test_zip_in_zip.html
new file mode 100644
index 000000000..7e381a1ef
--- /dev/null
+++ b/dom/archivereader/test/test_zip_in_zip.html
@@ -0,0 +1,111 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Archive Reader Zip-In-Zip Test</title>
+
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script type="text/javascript;version=1.7">
+ function filesToCreate() {
+ var binaryData = "";
+ for (var i = 0, len = binaryString.length / 2; i < len; ++i) {
+ var hex = binaryString[i * 2] + binaryString[i * 2 + 1];
+ binaryData += String.fromCharCode(parseInt(hex,16));
+ }
+ return [ {name: "fileArchiveReader_42.zip", data: binaryData} ];
+ }
+
+ function testSteps(files)
+ {
+ var binaryFile = files[0];
+
+ // The input is 4 nested zip archives:
+ doLoop(binaryFile, 4);
+
+ yield undefined;
+ }
+
+ function doLoop(blob, loop)
+ {
+ var r = new ArchiveReader(blob);
+ isnot(r, null, "ArchiveReader cannot be null");
+
+ // GetFilename
+ var handle = r.getFilenames();
+ isnot(handle, null, "ArchiveReader.getFilenames() cannot be null");
+ handle.onsuccess = function() {
+ ok(true, "ArchiveReader.getFilenames() should return a 'success'");
+ is(this.result instanceof Array, true, "ArchiveReader.getFilenames() should return an array");
+ is(this.result.length, 1, "ArchiveReader.getFilenames(): the array contains 1 item");
+ ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+
+ dump('Content:\n');
+ for (var i = 0; i < this.result.length; ++i)
+ dump(' * ' + this.result[i] + '\n');
+
+ var handle = r.getFile(this.result[0]);
+ handle.onerror = function() {
+ ok(false, "ArchiveReader.getFile() should not return any 'error'");
+ }
+ handle.onsuccess = function() {
+ ok(true, "ArchiveReader.getFilenames() should return a 'success'");
+ ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+
+ if (loop > 0)
+ doLoop(this.result, loop - 1);
+ else
+ doLastLoop(this.result);
+ }
+ }
+ handle.onerror = function() {
+ ok(false, "ArchiveReader.getFilenames() should not return any 'error'");
+ }
+ }
+
+ function doLastLoop(blob)
+ {
+ ok(blob.size == 262144, "The last file size is wrong");
+ ok(blob.name == 'empty.dat', "The last filename is wrong");
+ finishTest();
+ }
+
+ </script>
+ <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();">
+<p id="display">
+</p>
+<script type="text/javascript;version=1.7">
+var binaryString = '' +
+'504B03040A0000000000B0620E415F715F15970300009703000005001C00642E7A69705554090003AC262A50AC262A507578' +
+'0B000104E803000004E8030000504B03040A0000000000B0620E41CFE25F1EF7020000F702000005001C00632E7A69705554' +
+'090003AC262A50AC262A5075780B000104E803000004E8030000504B03040A0000000000B0620E4107D702A4570200005702' +
+'000005001C00622E7A69705554090003AC262A50AC262A5075780B000104E803000004E8030000504B03040A0000000000B0' +
+'620E417E45286DB7010000B701000005001C00612E7A69705554090003AC262A50AC262A5075780B000104E803000004E803' +
+'0000504B0304140000000800F7610E41784909B70F0100000000040009001C00656D7074792E646174555409000351252A50' +
+'57252A5075780B000104E803000004E8030000EDC13101000000C2A0FEA9E76D07A000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000DE00504B01021E0314000000' +
+'0800F7610E41784909B70F01000000000400090018000000000001000000B48100000000656D7074792E6461745554050003' +
+'51252A5075780B000104E803000004E8030000504B050600000000010001004F000000520100000000504B01021E030A0000' +
+'000000B0620E417E45286DB7010000B7010000050018000000000000000000B48100000000612E7A69705554050003AC262A' +
+'5075780B000104E803000004E8030000504B050600000000010001004B000000F60100000000504B01021E030A0000000000' +
+'B0620E4107D702A45702000057020000050018000000000000000000B48100000000622E7A69705554050003AC262A507578' +
+'0B000104E803000004E8030000504B050600000000010001004B000000960200000000504B01021E030A0000000000B0620E' +
+'41CFE25F1EF7020000F7020000050018000000000000000000B48100000000632E7A69705554050003AC262A5075780B0001' +
+'04E803000004E8030000504B050600000000010001004B000000360300000000504B01021E030A0000000000B0620E415F71' +
+'5F159703000097030000050018000000000000000000B48100000000642E7A69705554050003AC262A5075780B000104E803' +
+'000004E8030000504B050600000000010001004B000000D60300000000';
+</script>
+</body>
+
+</html>