michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set sw=4 ts=8 et tw=80 : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsJAR.h" michael@0: #include "nsJARChannel.h" michael@0: #include "nsJARProtocolHandler.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsEscape.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIViewSourceChannel.h" michael@0: #include "nsChannelProperties.h" michael@0: michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIFileURL.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/net/RemoteOpenFileChild.h" michael@0: #include "nsITabChild.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::net; michael@0: michael@0: static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); michael@0: michael@0: // the entry for a directory will either be empty (in the case of the michael@0: // top-level directory) or will end with a slash michael@0: #define ENTRY_IS_DIRECTORY(_entry) \ michael@0: ((_entry).IsEmpty() || '/' == (_entry).Last()) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // Ignore any LOG macro that we inherit from arbitrary headers. (We define our michael@0: // own LOG macro below.) michael@0: #ifdef LOG michael@0: #undef LOG michael@0: #endif michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // set NSPR_LOG_MODULES=nsJarProtocol:5 michael@0: // michael@0: static PRLogModuleInfo *gJarProtocolLog = nullptr; michael@0: #endif michael@0: michael@0: // If you ever want to define PR_FORCE_LOGGING in this file, see bug 545995 michael@0: #define LOG(args) PR_LOG(gJarProtocolLog, PR_LOG_DEBUG, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(gJarProtocolLog, 4) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsJARInputThunk michael@0: // michael@0: // this class allows us to do some extra work on the stream transport thread. michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsJARInputThunk : public nsIInputStream michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIINPUTSTREAM michael@0: michael@0: nsJARInputThunk(nsIZipReader *zipReader, michael@0: nsIURI* fullJarURI, michael@0: const nsACString &jarEntry, michael@0: bool usingJarCache) michael@0: : mUsingJarCache(usingJarCache) michael@0: , mJarReader(zipReader) michael@0: , mJarEntry(jarEntry) michael@0: , mContentLength(-1) michael@0: { michael@0: if (fullJarURI) { michael@0: #ifdef DEBUG michael@0: nsresult rv = michael@0: #endif michael@0: fullJarURI->GetAsciiSpec(mJarDirSpec); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "this shouldn't fail"); michael@0: } michael@0: } michael@0: michael@0: virtual ~nsJARInputThunk() michael@0: { michael@0: Close(); michael@0: } michael@0: michael@0: int64_t GetContentLength() michael@0: { michael@0: return mContentLength; michael@0: } michael@0: michael@0: nsresult Init(); michael@0: michael@0: private: michael@0: michael@0: bool mUsingJarCache; michael@0: nsCOMPtr mJarReader; michael@0: nsCString mJarDirSpec; michael@0: nsCOMPtr mJarStream; michael@0: nsCString mJarEntry; michael@0: int64_t mContentLength; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsJARInputThunk, nsIInputStream) michael@0: michael@0: nsresult michael@0: nsJARInputThunk::Init() michael@0: { michael@0: nsresult rv; michael@0: if (ENTRY_IS_DIRECTORY(mJarEntry)) { michael@0: // A directory stream also needs the Spec of the FullJarURI michael@0: // because is included in the stream data itself. michael@0: michael@0: NS_ENSURE_STATE(!mJarDirSpec.IsEmpty()); michael@0: michael@0: rv = mJarReader->GetInputStreamWithSpec(mJarDirSpec, michael@0: mJarEntry, michael@0: getter_AddRefs(mJarStream)); michael@0: } michael@0: else { michael@0: rv = mJarReader->GetInputStream(mJarEntry, michael@0: getter_AddRefs(mJarStream)); michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: // convert to the proper result if the entry wasn't found michael@0: // so that error pages work michael@0: if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) michael@0: rv = NS_ERROR_FILE_NOT_FOUND; michael@0: return rv; michael@0: } michael@0: michael@0: // ask the JarStream for the content length michael@0: uint64_t avail; michael@0: rv = mJarStream->Available((uint64_t *) &avail); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mContentLength = avail < INT64_MAX ? (int64_t) avail : -1; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputThunk::Close() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (mJarStream) michael@0: rv = mJarStream->Close(); michael@0: michael@0: if (!mUsingJarCache && mJarReader) michael@0: mJarReader->Close(); michael@0: michael@0: mJarReader = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputThunk::Available(uint64_t *avail) michael@0: { michael@0: return mJarStream->Available(avail); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputThunk::Read(char *buf, uint32_t count, uint32_t *countRead) michael@0: { michael@0: return mJarStream->Read(buf, count, countRead); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputThunk::ReadSegments(nsWriteSegmentFun writer, void *closure, michael@0: uint32_t count, uint32_t *countRead) michael@0: { michael@0: // stream transport does only calls Read() michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputThunk::IsNonBlocking(bool *nonBlocking) michael@0: { michael@0: *nonBlocking = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsJARChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: michael@0: nsJARChannel::nsJARChannel() michael@0: : mOpened(false) michael@0: , mAppURI(nullptr) michael@0: , mContentLength(-1) michael@0: , mLoadFlags(LOAD_NORMAL) michael@0: , mStatus(NS_OK) michael@0: , mIsPending(false) michael@0: , mIsUnsafe(true) michael@0: , mOpeningRemote(false) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: if (!gJarProtocolLog) michael@0: gJarProtocolLog = PR_NewLogModule("nsJarProtocol"); michael@0: #endif michael@0: michael@0: // hold an owning reference to the jar handler michael@0: NS_ADDREF(gJarHandler); michael@0: } michael@0: michael@0: nsJARChannel::~nsJARChannel() michael@0: { michael@0: // release owning reference to the jar handler michael@0: nsJARProtocolHandler *handler = gJarHandler; michael@0: NS_RELEASE(handler); // nullptr parameter michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel, michael@0: nsHashPropertyBag, michael@0: nsIRequest, michael@0: nsIChannel, michael@0: nsIStreamListener, michael@0: nsIRequestObserver, michael@0: nsIDownloadObserver, michael@0: nsIRemoteOpenFileListener, michael@0: nsIJARChannel) michael@0: michael@0: nsresult michael@0: nsJARChannel::Init(nsIURI *uri) michael@0: { michael@0: nsresult rv; michael@0: mJarURI = do_QueryInterface(uri, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mOriginalURI = mJarURI; michael@0: michael@0: // Prevent loading jar:javascript URIs (see bug 290982). michael@0: nsCOMPtr innerURI; michael@0: rv = mJarURI->GetJARFile(getter_AddRefs(innerURI)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: bool isJS; michael@0: rv = innerURI->SchemeIs("javascript", &isJS); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (isJS) { michael@0: NS_WARNING("blocking jar:javascript:"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: #if defined(PR_LOGGING) michael@0: mJarURI->GetSpec(mSpec); michael@0: #endif michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resultInput) michael@0: { michael@0: MOZ_ASSERT(resultInput); michael@0: michael@0: // important to pass a clone of the file since the nsIFile impl is not michael@0: // necessarily MT-safe michael@0: nsCOMPtr clonedFile; michael@0: nsresult rv = mJarFile->Clone(getter_AddRefs(clonedFile)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr reader; michael@0: if (jarCache) { michael@0: if (mInnerJarEntry.IsEmpty()) michael@0: rv = jarCache->GetZip(clonedFile, getter_AddRefs(reader)); michael@0: else michael@0: rv = jarCache->GetInnerZip(clonedFile, mInnerJarEntry, michael@0: getter_AddRefs(reader)); michael@0: } else { michael@0: // create an uncached jar reader michael@0: nsCOMPtr outerReader = do_CreateInstance(kZipReaderCID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = outerReader->Open(clonedFile); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (mInnerJarEntry.IsEmpty()) michael@0: reader = outerReader; michael@0: else { michael@0: reader = do_CreateInstance(kZipReaderCID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = reader->OpenInner(outerReader, mInnerJarEntry); michael@0: } michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsRefPtr input = new nsJARInputThunk(reader, michael@0: mJarURI, michael@0: mJarEntry, michael@0: jarCache != nullptr michael@0: ); michael@0: rv = input->Init(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Make GetContentLength meaningful michael@0: mContentLength = input->GetContentLength(); michael@0: michael@0: input.forget(resultInput); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsJARChannel::LookupFile() michael@0: { michael@0: LOG(("nsJARChannel::LookupFile [this=%x %s]\n", this, mSpec.get())); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr uri; michael@0: michael@0: rv = mJarURI->GetJARFile(getter_AddRefs(mJarBaseURI)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = mJarURI->GetJAREntry(mJarEntry); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // The name of the JAR entry must not contain URL-escaped characters: michael@0: // we're moving from URL domain to a filename domain here. nsStandardURL michael@0: // does basic escaping by default, which breaks reading zipped files which michael@0: // have e.g. spaces in their filenames. michael@0: NS_UnescapeURL(mJarEntry); michael@0: michael@0: // try to get a nsIFile directly from the url, which will often succeed. michael@0: { michael@0: nsCOMPtr fileURL = do_QueryInterface(mJarBaseURI); michael@0: if (fileURL) michael@0: fileURL->GetFile(getter_AddRefs(mJarFile)); michael@0: } michael@0: // if we're in child process and have special "remoteopenfile:://" scheme, michael@0: // create special nsIFile that gets file handle from parent when opened. michael@0: if (!mJarFile && !gJarHandler->IsMainProcess()) { michael@0: nsAutoCString scheme; michael@0: rv = mJarBaseURI->GetScheme(scheme); michael@0: if (NS_SUCCEEDED(rv) && scheme.EqualsLiteral("remoteopenfile")) { michael@0: nsRefPtr remoteFile = new RemoteOpenFileChild(); michael@0: rv = remoteFile->Init(mJarBaseURI, mAppURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mJarFile = remoteFile; michael@0: michael@0: nsIZipReaderCache *jarCache = gJarHandler->JarCache(); michael@0: if (jarCache) { michael@0: bool cached = false; michael@0: rv = jarCache->IsCached(mJarFile, &cached); michael@0: if (NS_SUCCEEDED(rv) && cached) { michael@0: // zipcache already has file mmapped: don't open on parent, michael@0: // just return and proceed to cache hit in CreateJarInput() michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: mOpeningRemote = true; michael@0: michael@0: if (gJarHandler->RemoteOpenFileInProgress(remoteFile, this)) { michael@0: // JarHandler will trigger OnRemoteFileOpen() after the first michael@0: // request for this file completes and we'll get a JAR cache michael@0: // hit. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Open file on parent: OnRemoteFileOpenComplete called when done michael@0: nsCOMPtr tabChild; michael@0: NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, tabChild); michael@0: rv = remoteFile->AsyncRemoteFileOpen(PR_RDONLY, this, tabChild.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: // try to handle a nested jar michael@0: if (!mJarFile) { michael@0: nsCOMPtr jarURI = do_QueryInterface(mJarBaseURI); michael@0: if (jarURI) { michael@0: nsCOMPtr fileURL; michael@0: nsCOMPtr innerJarURI; michael@0: rv = jarURI->GetJARFile(getter_AddRefs(innerJarURI)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: fileURL = do_QueryInterface(innerJarURI); michael@0: if (fileURL) { michael@0: fileURL->GetFile(getter_AddRefs(mJarFile)); michael@0: jarURI->GetJAREntry(mInnerJarEntry); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsJARChannel::OpenLocalFile() michael@0: { michael@0: MOZ_ASSERT(mIsPending); michael@0: michael@0: // Local files are always considered safe. michael@0: mIsUnsafe = false; michael@0: michael@0: nsRefPtr input; michael@0: nsresult rv = CreateJarInput(gJarHandler->JarCache(), michael@0: getter_AddRefs(input)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Create input stream pump and call AsyncRead as a block. michael@0: rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = mPump->AsyncRead(this, nullptr); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsJARChannel::NotifyError(nsresult aError) michael@0: { michael@0: MOZ_ASSERT(NS_FAILED(aError)); michael@0: michael@0: mStatus = aError; michael@0: michael@0: OnStartRequest(nullptr, nullptr); michael@0: OnStopRequest(nullptr, nullptr, aError); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIRequest michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetName(nsACString &result) michael@0: { michael@0: return mJarURI->GetSpec(result); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::IsPending(bool *result) michael@0: { michael@0: *result = mIsPending; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetStatus(nsresult *status) michael@0: { michael@0: if (mPump && NS_SUCCEEDED(mStatus)) michael@0: mPump->GetStatus(status); michael@0: else michael@0: *status = mStatus; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::Cancel(nsresult status) michael@0: { michael@0: mStatus = status; michael@0: if (mPump) michael@0: return mPump->Cancel(status); michael@0: michael@0: NS_ASSERTION(!mIsPending, "need to implement cancel when downloading"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::Suspend() michael@0: { michael@0: if (mPump) michael@0: return mPump->Suspend(); michael@0: michael@0: NS_ASSERTION(!mIsPending, "need to implement suspend when downloading"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::Resume() michael@0: { michael@0: if (mPump) michael@0: return mPump->Resume(); michael@0: michael@0: NS_ASSERTION(!mIsPending, "need to implement resume when downloading"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) michael@0: { michael@0: *aLoadFlags = mLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetLoadFlags(nsLoadFlags aLoadFlags) michael@0: { michael@0: mLoadFlags = aLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetLoadGroup(nsILoadGroup **aLoadGroup) michael@0: { michael@0: NS_IF_ADDREF(*aLoadGroup = mLoadGroup); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) michael@0: { michael@0: mLoadGroup = aLoadGroup; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetOriginalURI(nsIURI **aURI) michael@0: { michael@0: *aURI = mOriginalURI; michael@0: NS_ADDREF(*aURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetOriginalURI(nsIURI *aURI) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: mOriginalURI = aURI; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetURI(nsIURI **aURI) michael@0: { michael@0: if (mAppURI) { michael@0: NS_IF_ADDREF(*aURI = mAppURI); michael@0: } else { michael@0: NS_IF_ADDREF(*aURI = mJarURI); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetOwner(nsISupports **aOwner) michael@0: { michael@0: // JAR signatures are not processed to avoid main-thread network I/O (bug 726125) michael@0: *aOwner = mOwner; michael@0: NS_IF_ADDREF(*aOwner); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetOwner(nsISupports *aOwner) michael@0: { michael@0: mOwner = aOwner; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) michael@0: { michael@0: NS_IF_ADDREF(*aCallbacks = mCallbacks); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) michael@0: { michael@0: mCallbacks = aCallbacks; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo) michael@0: { michael@0: NS_PRECONDITION(aSecurityInfo, "Null out param"); michael@0: NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetContentType(nsACString &result) michael@0: { michael@0: // If the Jar file has not been open yet, michael@0: // We return application/x-unknown-content-type michael@0: if (!mOpened) { michael@0: result.Assign(UNKNOWN_CONTENT_TYPE); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mContentType.IsEmpty()) { michael@0: michael@0: // michael@0: // generate content type and set it michael@0: // michael@0: const char *ext = nullptr, *fileName = mJarEntry.get(); michael@0: int32_t len = mJarEntry.Length(); michael@0: michael@0: // check if we're displaying a directory michael@0: // mJarEntry will be empty if we're trying to display michael@0: // the topmost directory in a zip, e.g. jar:foo.zip!/ michael@0: if (ENTRY_IS_DIRECTORY(mJarEntry)) { michael@0: mContentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT); michael@0: } michael@0: else { michael@0: // not a directory, take a guess by its extension michael@0: for (int32_t i = len-1; i >= 0; i--) { michael@0: if (fileName[i] == '.') { michael@0: ext = &fileName[i + 1]; michael@0: break; michael@0: } michael@0: } michael@0: if (ext) { michael@0: nsIMIMEService *mimeServ = gJarHandler->MimeService(); michael@0: if (mimeServ) michael@0: mimeServ->GetTypeFromExtension(nsDependentCString(ext), mContentType); michael@0: } michael@0: if (mContentType.IsEmpty()) michael@0: mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); michael@0: } michael@0: } michael@0: result = mContentType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetContentType(const nsACString &aContentType) michael@0: { michael@0: // If someone gives us a type hint we should just use that type instead of michael@0: // doing our guessing. So we don't care when this is being called. michael@0: michael@0: // mContentCharset is unchanged if not parsed michael@0: NS_ParseContentType(aContentType, mContentType, mContentCharset); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetContentCharset(nsACString &aContentCharset) michael@0: { michael@0: // If someone gives us a charset hint we should just use that charset. michael@0: // So we don't care when this is being called. michael@0: aContentCharset = mContentCharset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetContentCharset(const nsACString &aContentCharset) michael@0: { michael@0: mContentCharset = aContentCharset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetContentDisposition(uint32_t *aContentDisposition) michael@0: { michael@0: if (mContentDispositionHeader.IsEmpty()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *aContentDisposition = mContentDisposition; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetContentDisposition(uint32_t aContentDisposition) michael@0: { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename) michael@0: { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename) michael@0: { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader) michael@0: { michael@0: if (mContentDispositionHeader.IsEmpty()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: aContentDispositionHeader = mContentDispositionHeader; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetContentLength(int64_t *result) michael@0: { michael@0: *result = mContentLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetContentLength(int64_t aContentLength) michael@0: { michael@0: // XXX does this really make any sense at all? michael@0: mContentLength = aContentLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::Open(nsIInputStream **stream) michael@0: { michael@0: LOG(("nsJARChannel::Open [this=%x]\n", this)); michael@0: michael@0: NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS); michael@0: NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); michael@0: michael@0: mJarFile = nullptr; michael@0: mIsUnsafe = true; michael@0: michael@0: nsresult rv = LookupFile(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // If mJarInput was not set by LookupFile, the JAR is a remote jar. michael@0: if (!mJarFile) { michael@0: NS_NOTREACHED("need sync downloader"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsRefPtr input; michael@0: rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: input.forget(stream); michael@0: mOpened = true; michael@0: // local files are always considered safe michael@0: mIsUnsafe = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx) michael@0: { michael@0: LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this)); michael@0: michael@0: NS_ENSURE_ARG_POINTER(listener); michael@0: NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS); michael@0: NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); michael@0: michael@0: mJarFile = nullptr; michael@0: mIsUnsafe = true; michael@0: michael@0: // Initialize mProgressSink michael@0: NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink); michael@0: michael@0: nsresult rv = LookupFile(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // These variables must only be set if we're going to trigger an michael@0: // OnStartRequest, either from AsyncRead or OnDownloadComplete. michael@0: // michael@0: // That means: Do not add early return statements beyond this point! michael@0: mListener = listener; michael@0: mListenerContext = ctx; michael@0: mIsPending = true; michael@0: michael@0: if (!mJarFile) { michael@0: // Not a local file... michael@0: // kick off an async download of the base URI... michael@0: rv = NS_NewDownloader(getter_AddRefs(mDownloader), this); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = NS_OpenURI(mDownloader, nullptr, mJarBaseURI, nullptr, michael@0: mLoadGroup, mCallbacks, michael@0: mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS)); michael@0: } else if (mOpeningRemote) { michael@0: // nothing to do: already asked parent to open file. michael@0: } else { michael@0: rv = OpenLocalFile(); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: mIsPending = false; michael@0: mListenerContext = nullptr; michael@0: mListener = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: if (mLoadGroup) michael@0: mLoadGroup->AddRequest(this, nullptr); michael@0: michael@0: mOpened = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIJARChannel michael@0: //----------------------------------------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsJARChannel::GetIsUnsafe(bool *isUnsafe) michael@0: { michael@0: *isUnsafe = mIsUnsafe; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::SetAppURI(nsIURI *aURI) { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: nsAutoCString scheme; michael@0: aURI->GetScheme(scheme); michael@0: if (!scheme.EqualsLiteral("app")) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: mAppURI = aURI; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIDownloadObserver michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::OnDownloadComplete(nsIDownloader *downloader, michael@0: nsIRequest *request, michael@0: nsISupports *context, michael@0: nsresult status, michael@0: nsIFile *file) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr channel(do_QueryInterface(request)); michael@0: if (channel) { michael@0: uint32_t loadFlags; michael@0: channel->GetLoadFlags(&loadFlags); michael@0: if (loadFlags & LOAD_REPLACE) { michael@0: mLoadFlags |= LOAD_REPLACE; michael@0: michael@0: if (!mOriginalURI) { michael@0: SetOriginalURI(mJarURI); michael@0: } michael@0: michael@0: nsCOMPtr innerURI; michael@0: rv = channel->GetURI(getter_AddRefs(innerURI)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr newURI; michael@0: rv = mJarURI->CloneWithJARFile(innerURI, michael@0: getter_AddRefs(newURI)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mJarURI = newURI; michael@0: } michael@0: } michael@0: if (NS_SUCCEEDED(status)) { michael@0: status = rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(status) && channel) { michael@0: // Grab the security info from our base channel michael@0: channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); michael@0: michael@0: nsCOMPtr httpChannel(do_QueryInterface(channel)); michael@0: if (httpChannel) { michael@0: // We only want to run scripts if the server really intended to michael@0: // send us a JAR file. Check the server-supplied content type for michael@0: // a JAR type. michael@0: nsAutoCString header; michael@0: httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"), michael@0: header); michael@0: nsAutoCString contentType; michael@0: nsAutoCString charset; michael@0: NS_ParseContentType(header, contentType, charset); michael@0: nsAutoCString channelContentType; michael@0: channel->GetContentType(channelContentType); michael@0: mIsUnsafe = !(contentType.Equals(channelContentType) && michael@0: (contentType.EqualsLiteral("application/java-archive") || michael@0: contentType.EqualsLiteral("application/x-jar"))); michael@0: } else { michael@0: nsCOMPtr innerJARChannel(do_QueryInterface(channel)); michael@0: if (innerJARChannel) { michael@0: bool unsafe; michael@0: innerJARChannel->GetIsUnsafe(&unsafe); michael@0: mIsUnsafe = unsafe; michael@0: } michael@0: } michael@0: michael@0: channel->GetContentDispositionHeader(mContentDispositionHeader); michael@0: mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(status) && mIsUnsafe && michael@0: !Preferences::GetBool("network.jar.open-unsafe-types", false)) { michael@0: status = NS_ERROR_UNSAFE_CONTENT_TYPE; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(status)) { michael@0: // Refuse to unpack view-source: jars even if open-unsafe-types is set. michael@0: nsCOMPtr viewSource = do_QueryInterface(channel); michael@0: if (viewSource) { michael@0: status = NS_ERROR_UNSAFE_CONTENT_TYPE; michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(status)) { michael@0: mJarFile = file; michael@0: michael@0: nsRefPtr input; michael@0: rv = CreateJarInput(nullptr, getter_AddRefs(input)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // create input stream pump michael@0: rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = mPump->AsyncRead(this, nullptr); michael@0: } michael@0: status = rv; michael@0: } michael@0: michael@0: if (NS_FAILED(status)) { michael@0: NotifyError(status); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIRemoteOpenFileListener michael@0: //----------------------------------------------------------------------------- michael@0: nsresult michael@0: nsJARChannel::OnRemoteFileOpenComplete(nsresult aOpenStatus) michael@0: { michael@0: nsresult rv = aOpenStatus; michael@0: michael@0: // NS_ERROR_ALREADY_OPENED here means we'll hit JAR cache in michael@0: // OpenLocalFile(). michael@0: if (NS_SUCCEEDED(rv) || rv == NS_ERROR_ALREADY_OPENED) { michael@0: rv = OpenLocalFile(); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: NotifyError(rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIStreamListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx) michael@0: { michael@0: LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec.get())); michael@0: michael@0: return mListener->OnStartRequest(this, mListenerContext); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::OnStopRequest(nsIRequest *req, nsISupports *ctx, nsresult status) michael@0: { michael@0: LOG(("nsJARChannel::OnStopRequest [this=%x %s status=%x]\n", michael@0: this, mSpec.get(), status)); michael@0: michael@0: if (NS_SUCCEEDED(mStatus)) michael@0: mStatus = status; michael@0: michael@0: if (mListener) { michael@0: mListener->OnStopRequest(this, mListenerContext, status); michael@0: mListener = 0; michael@0: mListenerContext = 0; michael@0: } michael@0: michael@0: if (mLoadGroup) michael@0: mLoadGroup->RemoveRequest(this, nullptr, status); michael@0: michael@0: mPump = 0; michael@0: mIsPending = false; michael@0: mDownloader = 0; // this may delete the underlying jar file michael@0: michael@0: // Drop notification callbacks to prevent cycles. michael@0: mCallbacks = 0; michael@0: mProgressSink = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARChannel::OnDataAvailable(nsIRequest *req, nsISupports *ctx, michael@0: nsIInputStream *stream, michael@0: uint64_t offset, uint32_t count) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: LOG(("nsJARChannel::OnDataAvailable [this=%x %s]\n", this, mSpec.get())); michael@0: #endif michael@0: michael@0: nsresult rv; michael@0: michael@0: rv = mListener->OnDataAvailable(this, mListenerContext, stream, offset, count); michael@0: michael@0: // simply report progress here instead of hooking ourselves up as a michael@0: // nsITransportEventSink implementation. michael@0: // XXX do the 64-bit stuff for real michael@0: if (mProgressSink && NS_SUCCEEDED(rv) && !(mLoadFlags & LOAD_BACKGROUND)) michael@0: mProgressSink->OnProgress(this, nullptr, offset + count, michael@0: uint64_t(mContentLength)); michael@0: michael@0: return rv; // let the pump cancel on failure michael@0: }