diff --git a/browser/base/content/test/performance/browser_preferences_usage.js b/browser/base/content/test/performance/browser_preferences_usage.js index 35824131d1fd..daf7c4048ffc 100644 --- a/browser/base/content/test/performance/browser_preferences_usage.js +++ b/browser/base/content/test/performance/browser_preferences_usage.js @@ -97,6 +97,10 @@ add_task(async function startup() { min: 20, max: 55, }, + // This seems to get called frequently only on infra. + "network.jar.block-remote-files": { + max: 500, + }, }; let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject; @@ -157,6 +161,8 @@ add_task(async function open_10_tabs() { min: 5, max: 20, }, + // This seems to get called frequently only on infra. + "network.jar.block-remote-files": { }, }; Services.prefs.resetStats(); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 6dfae81e053c..027161527cc0 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -1609,6 +1609,24 @@ nsDocShell::GetParentCharset(const Encoding*& aCharset, NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal); } +NS_IMETHODIMP +nsDocShell::GetChannelIsUnsafe(bool* aUnsafe) +{ + *aUnsafe = false; + + nsIChannel* channel = GetCurrentDocChannel(); + if (!channel) { + return NS_OK; + } + + nsCOMPtr jarChannel = do_QueryInterface(channel); + if (!jarChannel) { + return NS_OK; + } + + return jarChannel->GetIsUnsafe(aUnsafe); +} + NS_IMETHODIMP nsDocShell::GetHasMixedActiveContentLoaded(bool* aHasMixedActiveContentLoaded) { @@ -1667,6 +1685,12 @@ nsDocShell::GetAllowPlugins(bool* aAllowPlugins) NS_ENSURE_ARG_POINTER(aAllowPlugins); *aAllowPlugins = mAllowPlugins; + if (!mAllowPlugins) { + return NS_OK; + } + + bool unsafe; + *aAllowPlugins = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; return NS_OK; } @@ -1880,6 +1904,12 @@ nsDocShell::GetAllowMetaRedirects(bool* aReturn) NS_ENSURE_ARG_POINTER(aReturn); *aReturn = mAllowMetaRedirects; + if (!mAllowMetaRedirects) { + return NS_OK; + } + + bool unsafe; + *aReturn = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; return NS_OK; } @@ -9551,6 +9581,34 @@ nsDocShell::InternalLoad(nsIURI* aURI, } } + // Don't allow loads that would inherit our security context + // if this document came from an unsafe channel. + { + bool willInherit; + // This condition needs to match the one in + // nsContentUtils::ChannelShouldInheritPrincipal. + // Except we reverse the rv check to be safe in case + // nsContentUtils::URIInheritsSecurityContext fails here and + // succeeds there. + rv = nsContentUtils::URIInheritsSecurityContext(aURI, &willInherit); + if (NS_FAILED(rv) || willInherit || NS_IsAboutBlank(aURI)) { + nsCOMPtr treeItem = this; + do { + nsCOMPtr itemDocShell = do_QueryInterface(treeItem); + bool isUnsafe; + if (itemDocShell && + NS_SUCCEEDED(itemDocShell->GetChannelIsUnsafe(&isUnsafe)) && + isUnsafe) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr parent; + treeItem->GetSameTypeParent(getter_AddRefs(parent)); + parent.swap(treeItem); + } while (treeItem); + } + } + nsIDocument* doc = mContentViewer ? mContentViewer->GetDocument() : nullptr; diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index e5e8a19e46a2..202fcd92da2f 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -582,6 +582,13 @@ interface nsIDocShell : nsIDocShellTreeItem */ readonly attribute boolean isInUnload; + /** + * Find out if the currently loaded document came from a suspicious channel + * (such as a JAR channel where the server-returned content type isn't a + * known JAR type). + */ + readonly attribute boolean channelIsUnsafe; + /** * This attribute determines whether Mixed Active Content is loaded on the * document. When it is true, mixed active content was not blocked and has diff --git a/docshell/test/mochitest.ini b/docshell/test/mochitest.ini index 7de6ef7f77d5..00f5e94cb2fb 100644 --- a/docshell/test/mochitest.ini +++ b/docshell/test/mochitest.ini @@ -1,6 +1,8 @@ [DEFAULT] support-files = bug123696-subframe.html + bug369814.jar + bug369814.zip bug404548-subframe.html bug404548-subframe_window.html bug413310-post.sjs @@ -14,6 +16,7 @@ support-files = file_anchor_scroll_after_document_open.html file_bfcache_plus_hash_1.html file_bfcache_plus_hash_2.html + file_bug369814.html file_bug385434_1.html file_bug385434_2.html file_bug385434_3.html @@ -54,6 +57,7 @@ support-files = [test_anchor_scroll_after_document_open.html] [test_bfcache_plus_hash.html] [test_bug123696.html] +[test_bug369814.html] [test_bug384014.html] [test_bug385434.html] [test_bug387979.html] diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index adc0921f35ff..f060fcaf37aa 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -2005,6 +2005,14 @@ nsGlobalWindowOuter::SetNewDocument(nsIDocument* aDocument, NS_ENSURE_SUCCESS(rv, rv); } + // If the document comes from a JAR, check if the channel was determined + // to be unsafe. If so, permanently disable script on the compartment by + // calling Block() and throwing away the key. + nsCOMPtr jarChannel = do_QueryInterface(aDocument->GetChannel()); + if (jarChannel && jarChannel->GetIsUnsafe()) { + xpc::Scriptability::Get(newInnerGlobal).Block(); + } + if (mArguments) { newInnerWindow->DefineArgumentsProperty(mArguments); mArguments = nullptr; diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 78a0f77bc923..ff352a2a0aff 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -121,6 +121,7 @@ support-files = file_bug769117.html file_bug782342.txt file_bug787778.sjs + file_bug804395.jar file_bug869432.eventsource file_bug869432.eventsource^headers^ file_bug907892.html @@ -544,6 +545,7 @@ skip-if = toolkit == 'android' #bug 687032 [test_bug787778.html] [test_bug789315.html] [test_bug789856.html] +[test_bug804395.html] [test_bug809003.html] [test_bug810494.html] [test_bug811701.html] diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini index 05be426cae0d..cc2bd3e31ffb 100644 --- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -19,6 +19,8 @@ support-files = bug340800_iframe.txt bug369370-popup.png bug372098-link-target.html + bug392567.jar + bug392567.jar^headers^ bug441930_iframe.html bug445004-inner.html bug445004-inner.js @@ -249,6 +251,7 @@ skip-if = toolkit == 'android' #TIMED_OUT [test_bug389797.html] [test_bug390975.html] [test_bug391994.html] +[test_bug392567.html] [test_bug394700.html] [test_bug395107.html] [test_bug401160.xhtml] diff --git a/dom/security/test/cors/mochitest.ini b/dom/security/test/cors/mochitest.ini index 7dcdc05c7178..095b0d09d7d5 100644 --- a/dom/security/test/cors/mochitest.ini +++ b/dom/security/test/cors/mochitest.ini @@ -2,6 +2,7 @@ support-files = file_CrossSiteXHR_cache_server.sjs file_CrossSiteXHR_inner.html + file_CrossSiteXHR_inner.jar file_CrossSiteXHR_inner_data.sjs file_CrossSiteXHR_server.sjs diff --git a/dom/security/test/cors/test_CrossSiteXHR_origin.html b/dom/security/test/cors/test_CrossSiteXHR_origin.html index 447fc383c02f..7d31a4f81894 100644 --- a/dom/security/test/cors/test_CrossSiteXHR_origin.html +++ b/dom/security/test/cors/test_CrossSiteXHR_origin.html @@ -39,6 +39,9 @@ var origins = { server: 'http://\u03c0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1.\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae', origin: 'http://xn--hxajbheg2az3al.xn--jxalpdlp' }, + { origin: 'http://example.org', + file: 'jar:http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_inner.jar!/file_CrossSiteXHR_inner.html' + }, { origin: 'null', file: 'http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_inner_data.sjs' }, @@ -159,7 +162,9 @@ function* runTest() { } addLoadEvent(function() { - gen.next(); + SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, function() { + gen.next(); + }); }); diff --git a/dom/tests/mochitest/whatwg/mochitest.ini b/dom/tests/mochitest/whatwg/mochitest.ini index 9cb6a4ee15a7..ab853b86cf58 100644 --- a/dom/tests/mochitest/whatwg/mochitest.ini +++ b/dom/tests/mochitest/whatwg/mochitest.ini @@ -6,6 +6,8 @@ support-files = postMessage_hash.html postMessage_helper.html postMessage_idn_helper.html + postMessage.jar + postMessage.jar^headers^ postMessage_joined_helper2.html postMessage_joined_helper.html postMessage_onOther.html @@ -27,6 +29,7 @@ skip-if = toolkit == 'android' #bug 894914 - wrong data - got FAIL, expected mes [test_postMessage_hash.html] [test_postMessage.html] [test_postMessage_idn.xhtml] +[test_postMessage_jar.html] [test_postMessage_joined.html] [test_postMessage_onOther.html] [test_postMessage_origin.xhtml] diff --git a/dom/tests/mochitest/whatwg/postMessage.jar b/dom/tests/mochitest/whatwg/postMessage.jar new file mode 100644 index 000000000000..d4ee4abc3d19 Binary files /dev/null and b/dom/tests/mochitest/whatwg/postMessage.jar differ diff --git a/modules/libjar/test/mochitest/bug1173171.zip^headers^ b/dom/tests/mochitest/whatwg/postMessage.jar^headers^ similarity index 100% rename from modules/libjar/test/mochitest/bug1173171.zip^headers^ rename to dom/tests/mochitest/whatwg/postMessage.jar^headers^ diff --git a/dom/tests/mochitest/whatwg/test_postMessage_jar.html b/dom/tests/mochitest/whatwg/test_postMessage_jar.html new file mode 100644 index 000000000000..e83dc19f08b0 --- /dev/null +++ b/dom/tests/mochitest/whatwg/test_postMessage_jar.html @@ -0,0 +1,52 @@ + + + + + postMessage's interaction with pages at jar: URIs + + + + + + +Mozilla Bug 430251 +

+ + +
+
+
+ + diff --git a/dom/workers/test/browser.ini b/dom/workers/test/browser.ini index c9fd854b541a..ae1e27d13a70 100644 --- a/dom/workers/test/browser.ini +++ b/dom/workers/test/browser.ini @@ -5,6 +5,7 @@ support-files = frame_script.js head.js !/dom/base/test/file_empty.html + !/dom/base/test/file_bug945152.jar [browser_bug1047663.js] [browser_bug1104623.js] diff --git a/dom/workers/test/bug1063538.sjs b/dom/workers/test/bug1063538.sjs deleted file mode 100644 index 5d2570747be7..000000000000 --- a/dom/workers/test/bug1063538.sjs +++ /dev/null @@ -1,6 +0,0 @@ -function handleRequest(request, response) { - response.processAsync(); - response.write("Hello"); - setTimeout(function() { response.finish(); }, 100000); // wait 100 seconds. -} - diff --git a/dom/workers/test/bug1063538_worker.js b/dom/workers/test/bug1063538_worker.js index caf03af0044f..dc53dd2891aa 100644 --- a/dom/workers/test/bug1063538_worker.js +++ b/dom/workers/test/bug1063538_worker.js @@ -3,7 +3,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -var gURL = "http://example.org/tests/dom/workers/test/bug1063538.sjs"; +var gJar = "jar:http://example.org/tests/dom/base/test/file_bug945152.jar!/data_big.txt"; var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true}); var progressFired = false; @@ -20,6 +20,6 @@ xhr.onprogress = function(e) { }; onmessage = function(e) { - xhr.open("GET", gURL, true); + xhr.open("GET", gJar, true); xhr.send(); } diff --git a/dom/workers/test/mochitest.ini b/dom/workers/test/mochitest.ini index 95f9baf9821e..087d774a2908 100644 --- a/dom/workers/test/mochitest.ini +++ b/dom/workers/test/mochitest.ini @@ -94,6 +94,7 @@ support-files = script_createFile.js worker_suspended.js window_suspended.html + !/dom/base/test/file_bug945152.jar !/dom/notification/test/mochitest/MockServices.js !/dom/notification/test/mochitest/NotificationTest.js !/dom/xhr/tests/relativeLoad_import.js diff --git a/dom/workers/test/test_bug1063538.html b/dom/workers/test/test_bug1063538.html index 7d20bdd5f10a..7c32b8ed3bcf 100644 --- a/dom/workers/test/test_bug1063538.html +++ b/dom/workers/test/test_bug1063538.html @@ -38,7 +38,9 @@ function runTest() { SimpleTest.waitForExplicitFinish(); addLoadEvent(function() { - SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest); + SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, function() { + SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest); + }); }); diff --git a/modules/libjar/nsIJARChannel.idl b/modules/libjar/nsIJARChannel.idl index 765752e933d8..c163f1369cdd 100644 --- a/modules/libjar/nsIJARChannel.idl +++ b/modules/libjar/nsIJARChannel.idl @@ -11,6 +11,14 @@ interface nsIZipEntry; [scriptable, builtinclass, uuid(e72b179b-d5df-4d87-b5de-fd73a65c60f6)] interface nsIJARChannel : nsIChannel { + /** + * Returns TRUE if the JAR file is not safe (if the content type reported + * by the server for a remote JAR is not of an expected type). Scripting, + * redirects, and plugins should be disabled when loading from this + * channel. + */ + [infallible] readonly attribute boolean isUnsafe; + /** * Returns the JAR file. May be null if the jar is remote. * Setting the JAR file is optional and overrides the JAR diff --git a/modules/libjar/nsJARChannel.cpp b/modules/libjar/nsJARChannel.cpp index be3fce93d990..5efd8deba1f2 100644 --- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -194,14 +194,19 @@ nsJARInputThunk::IsNonBlocking(bool *nonBlocking) nsJARChannel::nsJARChannel() : mOpened(false) + , mContentDisposition(0) , mContentLength(-1) , mLoadFlags(LOAD_NORMAL) , mStatus(NS_OK) , mIsPending(false) , mEnableOMT(true) , mPendingEvent() + , mIsUnsafe(true) + , mBlockRemoteFiles(false) { LOG(("nsJARChannel::nsJARChannel [this=%p]\n", this)); + mBlockRemoteFiles = Preferences::GetBool("network.jar.block-remote-files", false); + // hold an owning reference to the jar handler mJarHandler = gJarHandler; } @@ -263,7 +268,7 @@ nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resu { LOG(("nsJARChannel::CreateJarInput [this=%p]\n", this)); MOZ_ASSERT(resultInput); - MOZ_ASSERT(mJarFile); + MOZ_ASSERT(mJarFile || mTempMem); // important to pass a clone of the file since the nsIFile impl is not // necessarily MT-safe @@ -279,6 +284,7 @@ nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resu if (mPreCachedJarReader) { reader = mPreCachedJarReader; } else if (jarCache) { + MOZ_ASSERT(mJarFile); if (mInnerJarEntry.IsEmpty()) rv = jarCache->GetZip(clonedFile, getter_AddRefs(reader)); else @@ -290,7 +296,12 @@ nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resu if (NS_FAILED(rv)) return rv; - rv = outerReader->Open(clonedFile); + if (mJarFile) { + rv = outerReader->Open(clonedFile); + } else { + rv = outerReader->OpenMemory(mTempMem->Elements(), + mTempMem->Length()); + } if (NS_FAILED(rv)) return rv; @@ -433,6 +444,9 @@ nsJARChannel::OpenLocalFile() MOZ_ASSERT(mIsPending); MOZ_ASSERT(mJarFile); + // Local files are always considered safe. + mIsUnsafe = false; + nsresult rv; // Set mLoadGroup and mOpened before AsyncOpen return, and set back if @@ -885,7 +899,11 @@ nsJARChannel::SetContentCharset(const nsACString &aContentCharset) NS_IMETHODIMP nsJARChannel::GetContentDisposition(uint32_t *aContentDisposition) { - return NS_ERROR_NOT_AVAILABLE; + if (mContentDispositionHeader.IsEmpty()) + return NS_ERROR_NOT_AVAILABLE; + + *aContentDisposition = mContentDisposition; + return NS_OK; } NS_IMETHODIMP @@ -909,7 +927,11 @@ nsJARChannel::SetContentDispositionFilename(const nsAString &aContentDisposition NS_IMETHODIMP nsJARChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader) { - return NS_ERROR_NOT_AVAILABLE; + if (mContentDispositionHeader.IsEmpty()) + return NS_ERROR_NOT_AVAILABLE; + + aContentDispositionHeader = mContentDispositionHeader; + return NS_OK; } NS_IMETHODIMP @@ -936,14 +958,15 @@ nsJARChannel::Open(nsIInputStream **stream) NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); mJarFile = nullptr; + mIsUnsafe = true; nsresult rv = LookupFile(); if (NS_FAILED(rv)) return rv; - // If mJarFile was not set by LookupFile, we can't open a channel. + // If mJarInput was not set by LookupFile, the JAR is a remote jar. if (!mJarFile) { - NS_NOTREACHED("only file-backed jars are supported"); + NS_NOTREACHED("need sync downloader"); return NS_ERROR_NOT_IMPLEMENTED; } @@ -954,6 +977,8 @@ nsJARChannel::Open(nsIInputStream **stream) input.forget(stream); mOpened = true; + // local files are always considered safe + mIsUnsafe = false; return NS_OK; } @@ -978,11 +1003,14 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx) nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), "security flags in loadInfo but asyncOpen2() not called"); + LOG(("nsJARChannel::AsyncOpen [this=%p]\n", this)); + NS_ENSURE_ARG_POINTER(listener); NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS); NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); mJarFile = nullptr; + mIsUnsafe = true; // Initialize mProgressSink NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink); @@ -992,17 +1020,6 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx) mIsPending = true; nsresult rv = LookupFile(); - if (NS_FAILED(rv) || !mJarFile) { - // Not a local file... - mIsPending = false; - mListenerContext = nullptr; - mListener = nullptr; - mCallbacks = nullptr; - mProgressSink = nullptr; - return mJarFile ? rv : NS_ERROR_UNSAFE_CONTENT_TYPE; - } - - rv = OpenLocalFile(); if (NS_FAILED(rv)) { mIsPending = false; mListenerContext = nullptr; @@ -1012,6 +1029,70 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx) return rv; } + nsCOMPtr channel; + + if (!mJarFile) { + // Not a local file... + + // Check preferences to see if all remote jar support should be disabled + if (mBlockRemoteFiles) { + mIsUnsafe = true; + mIsPending = false; + mListenerContext = nullptr; + mListener = nullptr; + mCallbacks = nullptr; + mProgressSink = nullptr; + return NS_ERROR_UNSAFE_CONTENT_TYPE; + } + + // kick off an async download of the base URI... + nsCOMPtr downloader = new MemoryDownloader(this); + uint32_t loadFlags = + mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS); + rv = NS_NewChannelInternal(getter_AddRefs(channel), + mJarBaseURI, + mLoadInfo, + nullptr, // PerformanceStorage + mLoadGroup, + mCallbacks, + loadFlags); + if (NS_FAILED(rv)) { + mIsPending = false; + mListenerContext = nullptr; + mListener = nullptr; + mCallbacks = nullptr; + mProgressSink = nullptr; + return rv; + } + if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) { + rv = channel->AsyncOpen2(downloader); + } + else { + rv = channel->AsyncOpen(downloader, nullptr); + } + + } + else { + rv = OpenLocalFile(); + if (NS_SUCCEEDED(rv)) { + return NS_OK; + } + } + + if (NS_FAILED(rv)) { + mIsPending = false; + mListenerContext = nullptr; + mListener = nullptr; + mCallbacks = nullptr; + mProgressSink = nullptr; + return rv; + } + + if (mLoadGroup) + mLoadGroup->AddRequest(this, nullptr); + + mOpened = true; + LOG(("nsJARChannel::AsyncOpen [this=%p] 8\n", this)); return NS_OK; } @@ -1019,23 +1100,30 @@ NS_IMETHODIMP nsJARChannel::AsyncOpen2(nsIStreamListener *aListener) { LOG(("nsJARChannel::AsyncOpen2 [this=%p]\n", this)); - nsCOMPtr listener = aListener; - nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); - if (NS_FAILED(rv)) { - mIsPending = false; - mListenerContext = nullptr; - mListener = nullptr; - mCallbacks = nullptr; - mProgressSink = nullptr; - return rv; - } + nsCOMPtr listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + if (NS_FAILED(rv)) { + mIsPending = false; + mListenerContext = nullptr; + mListener = nullptr; + mCallbacks = nullptr; + mProgressSink = nullptr; + return rv; + } - return AsyncOpen(listener, nullptr); + return AsyncOpen(listener, nullptr); } //----------------------------------------------------------------------------- // nsIJARChannel //----------------------------------------------------------------------------- +NS_IMETHODIMP +nsJARChannel::GetIsUnsafe(bool *isUnsafe) +{ + *isUnsafe = mIsUnsafe; + return NS_OK; +} + NS_IMETHODIMP nsJARChannel::GetJarFile(nsIFile **aFile) { @@ -1122,6 +1210,116 @@ nsJARChannel::GetZipEntry(nsIZipEntry **aZipEntry) return reader->GetEntry(mJarEntry, aZipEntry); } +//----------------------------------------------------------------------------- +// mozilla::net::MemoryDownloader::IObserver +//----------------------------------------------------------------------------- + +void +nsJARChannel::OnDownloadComplete(MemoryDownloader* aDownloader, + nsIRequest *request, + nsISupports *context, + nsresult status, + MemoryDownloader::Data aData) +{ + nsresult rv; + + nsCOMPtr channel(do_QueryInterface(request)); + if (channel) { + uint32_t loadFlags; + channel->GetLoadFlags(&loadFlags); + if (loadFlags & LOAD_REPLACE) { + // Update our URI to reflect any redirects that happen during + // the HTTP request. + if (!mOriginalURI) { + SetOriginalURI(mJarURI); + } + + nsCOMPtr innerURI; + rv = channel->GetURI(getter_AddRefs(innerURI)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr newURI; + rv = mJarURI->CloneWithJARFile(innerURI, + getter_AddRefs(newURI)); + if (NS_SUCCEEDED(rv)) { + mJarURI = newURI; + } + } + if (NS_SUCCEEDED(status)) { + status = rv; + } + } + } + + if (NS_SUCCEEDED(status) && channel) { + // In case the load info object has changed during a redirect, + // grab it from the target channel. + channel->GetLoadInfo(getter_AddRefs(mLoadInfo)); + // Grab the security info from our base channel + channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); + + nsCOMPtr httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + // We only want to run scripts if the server really intended to + // send us a JAR file. Check the server-supplied content type for + // a JAR type. + nsAutoCString header; + Unused << httpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("Content-Type"), header); + nsAutoCString contentType; + nsAutoCString charset; + NS_ParseResponseContentType(header, contentType, charset); + nsAutoCString channelContentType; + channel->GetContentType(channelContentType); + mIsUnsafe = !(contentType.Equals(channelContentType) && + (contentType.EqualsLiteral("application/java-archive") || + contentType.EqualsLiteral("application/x-jar"))); + } else { + nsCOMPtr innerJARChannel(do_QueryInterface(channel)); + if (innerJARChannel) { + mIsUnsafe = innerJARChannel->GetIsUnsafe(); + } + } + + channel->GetContentDispositionHeader(mContentDispositionHeader); + mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this); + } + + // This is a defense-in-depth check for the preferences to see if all remote jar + // support should be disabled. This check may not be needed. + MOZ_RELEASE_ASSERT(!mBlockRemoteFiles); + + if (NS_SUCCEEDED(status) && mIsUnsafe && + !Preferences::GetBool("network.jar.open-unsafe-types", false)) { + status = NS_ERROR_UNSAFE_CONTENT_TYPE; + } + + if (NS_SUCCEEDED(status)) { + // Refuse to unpack view-source: jars even if open-unsafe-types is set. + nsCOMPtr viewSource = do_QueryInterface(channel); + if (viewSource) { + status = NS_ERROR_UNSAFE_CONTENT_TYPE; + } + } + + if (NS_SUCCEEDED(status)) { + mTempMem = Move(aData); + + RefPtr input; + rv = CreateJarInput(nullptr, getter_AddRefs(input)); + if (NS_SUCCEEDED(rv)) { + // create input stream pump + rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input.forget()); + if (NS_SUCCEEDED(rv)) + rv = mPump->AsyncRead(this, nullptr); + } + status = rv; + } + + if (NS_FAILED(status)) { + NotifyError(status); + } +} + //----------------------------------------------------------------------------- // nsIStreamListener //----------------------------------------------------------------------------- diff --git a/modules/libjar/nsJARChannel.h b/modules/libjar/nsJARChannel.h index 0d28d6065ef3..9feb87f79092 100644 --- a/modules/libjar/nsJARChannel.h +++ b/modules/libjar/nsJARChannel.h @@ -33,6 +33,7 @@ class nsInputStreamPump; //----------------------------------------------------------------------------- class nsJARChannel final : public nsIJARChannel + , public mozilla::net::MemoryDownloader::IObserver , public nsIStreamListener , public nsIThreadRetargetableRequest , public nsIThreadRetargetableStreamListener @@ -65,6 +66,12 @@ private: nsresult CheckPendingEvents(); void NotifyError(nsresult aError); void FireOnProgress(uint64_t aProgress); + virtual void OnDownloadComplete(mozilla::net::MemoryDownloader* aDownloader, + nsIRequest* aRequest, + nsISupports* aCtxt, + nsresult aStatus, + mozilla::net::MemoryDownloader::Data aData) + override; nsCString mSpec; @@ -83,6 +90,10 @@ private: nsCOMPtr mListenerContext; nsCString mContentType; nsCString mContentCharset; + nsCString mContentDispositionHeader; + /* mContentDisposition is uninitialized if mContentDispositionHeader is + * empty */ + uint32_t mContentDisposition; int64_t mContentLength; uint32_t mLoadFlags; nsresult mStatus; @@ -95,6 +106,9 @@ private: uint32_t suspendCount; } mPendingEvent; + bool mIsUnsafe; + + mozilla::net::MemoryDownloader::Data mTempMem; nsCOMPtr mPump; // mRequest is only non-null during OnStartRequest, so we'll have a pointer // to the request if we get called back via RetargetDeliveryTo. @@ -108,6 +122,9 @@ private: // use StreamTransportService as background thread nsCOMPtr mWorker; + + // True if this channel should not download any remote files. + bool mBlockRemoteFiles; }; #endif // nsJARChannel_h__ diff --git a/modules/libjar/test/mochitest/bug1173171.zip b/modules/libjar/test/mochitest/bug403331.zip similarity index 100% rename from modules/libjar/test/mochitest/bug1173171.zip rename to modules/libjar/test/mochitest/bug403331.zip diff --git a/modules/libjar/test/mochitest/bug403331.zip^headers^ b/modules/libjar/test/mochitest/bug403331.zip^headers^ new file mode 100644 index 000000000000..28b8aa0a57d9 --- /dev/null +++ b/modules/libjar/test/mochitest/bug403331.zip^headers^ @@ -0,0 +1 @@ +Content-Type: application/java-archive diff --git a/modules/libjar/test/mochitest/mochitest.ini b/modules/libjar/test/mochitest/mochitest.ini index 71c315b91b59..0ae466ccabbb 100644 --- a/modules/libjar/test/mochitest/mochitest.ini +++ b/modules/libjar/test/mochitest/mochitest.ini @@ -1,5 +1,11 @@ - -[test_bug1173171.html] +[DEFAULT] support-files = - bug1173171.zip - bug1173171.zip^headers^ \ No newline at end of file + bug403331.zip + bug403331.zip^headers^ + openredirect.sjs + !/dom/base/test/file_bug945152.jar + +[test_bug403331.html] +[test_bug1034143_mapped.html] +run-if = os == 'linux' +[test_bug1173171.html] \ No newline at end of file diff --git a/modules/libjar/test/mochitest/openredirect.sjs b/modules/libjar/test/mochitest/openredirect.sjs new file mode 100644 index 000000000000..b6249cadff1b --- /dev/null +++ b/modules/libjar/test/mochitest/openredirect.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) +{ + response.setStatusLine(request.httpVersion, 301, "Moved Permanently"); + response.setHeader("Location", request.queryString, false); +} diff --git a/modules/libjar/test/mochitest/test_bug1034143_mapped.html b/modules/libjar/test/mochitest/test_bug1034143_mapped.html new file mode 100644 index 000000000000..109b785b6d6f --- /dev/null +++ b/modules/libjar/test/mochitest/test_bug1034143_mapped.html @@ -0,0 +1,53 @@ + + + + + + Test for Bug 945152 + + + + +Mozilla Bug 1034143 +

+ +
+
+
+ + diff --git a/modules/libjar/test/mochitest/test_bug1173171.html b/modules/libjar/test/mochitest/test_bug1173171.html index 32b37e560096..10a3676e8202 100644 --- a/modules/libjar/test/mochitest/test_bug1173171.html +++ b/modules/libjar/test/mochitest/test_bug1173171.html @@ -18,6 +18,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1173171 /** Test for Bug 1173171 **/ +// __setPref(key, value)__. +// Set a pref value asynchronously, returning a prmoise that resolves +// when it succeeds. +let pushPref = function (key, value) { + return new Promise(function(resolve, reject) { + SpecialPowers.pushPrefEnv({"set": [[key, value]]}, resolve); + }); +}; + // __xhr(method, url, responseType__. // A simple async XMLHttpRequest call. // Returns a promise with the response. @@ -40,9 +49,12 @@ let jarURL = "jar:http://mochi.test:8888/tests/modules/libjar/test/mochitest/bug // Test behavior when blocking is deactivated and activated. add_task(async function() { - let shouldBlock = true; - let response = await xhr("GET", jarURL, "document"); - is(response, null, "Remote jars should be blocked."); + for (let shouldBlock of [false, true]) { + await pushPref("network.jar.block-remote-files", shouldBlock); + let response = await xhr("GET", jarURL, "document"); + ok(shouldBlock === (response === null), + "Remote jars should be blocked if and only if the 'network.jar.block-remote-files' pref is active."); + } }); diff --git a/modules/libjar/test/mochitest/test_bug403331.html b/modules/libjar/test/mochitest/test_bug403331.html new file mode 100644 index 000000000000..b292c4c9ba58 --- /dev/null +++ b/modules/libjar/test/mochitest/test_bug403331.html @@ -0,0 +1,47 @@ + + + + + Test for Bug 403331 + + + + + + + +
+
+
+ + + diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 2731a49d784a..7b3b0c59591f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1892,6 +1892,13 @@ pref("network.websocket.delay-failed-reconnects", true); // Equal to the DEFAULT_RECONNECTION_TIME_VALUE value in nsEventSource.cpp pref("dom.server-events.default-reconnection-time", 5000); // in milliseconds +// If false, remote JAR files that are served with a content type other than +// application/java-archive or application/x-jar will not be opened +// by the jar channel. +pref("network.jar.open-unsafe-types", false); +// If true, loading remote JAR files using the jar: protocol will be prevented. +pref("network.jar.block-remote-files", true); + // This preference, if true, causes all UTF-8 domain names to be normalized to // punycode. The intention is to allow UTF-8 domain names as input, but never // generate them from punycode.