Bug 369814: don't open jar: content unless served from a safe mime type. r=bz, sr=dveditz, ui-r=beltzner
authordcamp@mozilla.com
Mon, 26 Nov 2007 21:32:23 -0800
changeset 8363 be54f6bb9e1e4be342d9d53b7342b5499753bd02
parent 8362 41ea0ce69d86b6abb987f87359cf1bc55f8cde88
child 8364 736cdbcdbe938a0f850f92aea1e78dbb64217e41
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, dveditz, beltzner
bugs369814
milestone1.9b2pre
Bug 369814: don't open jar: content unless served from a safe mime type. r=bz, sr=dveditz, ui-r=beltzner
browser/locales/en-US/chrome/overrides/appstrings.properties
browser/locales/en-US/chrome/overrides/netError.dtd
docshell/base/Makefile.in
docshell/base/nsDocShell.cpp
docshell/base/nsIDocShell.idl
docshell/base/nsWebShell.cpp
docshell/resources/content/netError.xhtml
docshell/test/Makefile.in
docshell/test/bug369814.zip
docshell/test/test_bug369814.html
dom/locales/en-US/chrome/appstrings.properties
dom/locales/en-US/chrome/netError.dtd
modules/libjar/nsIJARChannel.idl
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
modules/libpref/src/init/all.js
netwerk/base/public/nsNetError.h
testing/mochitest/tests/SimpleTest/EventUtils.js
--- a/browser/locales/en-US/chrome/overrides/appstrings.properties
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -48,16 +48,17 @@ resendButton.label=Resend
 unknownSocketType=Firefox doesn't know how to communicate with the server.
 netReset=The connection to the server was reset while the page was loading.
 netOffline=Firefox is currently in offline mode and can't browse the Web.
 isprinting=The document cannot change while Printing or in Print Preview.
 deniedPortAccess=This address uses a network port which is normally used for purposes other than Web browsing. Firefox has canceled the request for your protection.
 proxyResolveFailure=Firefox is configured to use a proxy server that can't be found.
 proxyConnectFailure=Firefox is configured to use a proxy server that is refusing connections.
 contentEncodingError=The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression. Please contact the website owners to inform them of this problem.
+unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 phishingBlocked=The web site at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -61,16 +61,23 @@
 
 <!ENTITY contentEncodingError.title "Content Encoding Error">
 <!ENTITY contentEncodingError.longDesc "
 <ul>
   <li>Please contact the website owners to inform them of this problem.</li>
 </ul>
 ">
 
+<!ENTITY unsafeContentType.title "Unsafe File Type">
+<!ENTITY unsafeContentType.longDesc "
+<ul>
+  <li>Please contact the website owners to inform them of this problem.</li>
+</ul>
+">
+
 <!ENTITY netReset.title "The connection was reset">
 <!ENTITY netReset.longDesc "&sharedLongDesc;">
 
 <!ENTITY netTimeout.title "The connection has timed out">
 <!ENTITY netTimeout.longDesc "&sharedLongDesc;">
 
 <!ENTITY protocolNotFound.title "The address wasn't understood">
 <!ENTITY protocolNotFound.longDesc "
--- a/docshell/base/Makefile.in
+++ b/docshell/base/Makefile.in
@@ -80,16 +80,17 @@ REQUIRES	= xpcom \
 		  find \
 		  nkcache \
 		  composer \
 		  commandhandler \
 		  editor \
 		  windowwatcher \
 		  imglib2 \
 		  mimetype \
+		  jar \
 		  $(NULL)
 
 SDK_XPIDLSRCS = \
 		nsIGlobalHistory.idl \
 		$(NULL)
 
 XPIDLSRCS	= \
 		nsCDocShell.idl			\
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -163,16 +163,18 @@
 // used to dispatch urls to default protocol handlers
 #include "nsCExternalHandlerService.h"
 #include "nsIExternalProtocolService.h"
 
 #include "nsIFocusController.h"
 
 #include "nsITextToSubURI.h"
 
+#include "nsIJARChannel.h"
+
 #include "prlog.h"
 #include "prmem.h"
 
 #include "nsISelectionDisplay.h"
 
 #include "nsIGlobalHistory2.h"
 #include "nsIGlobalHistory3.h"
 
@@ -1296,21 +1298,46 @@ NS_IMETHODIMP
 nsDocShell::SetDocumentCharsetInfo(nsIDocumentCharsetInfo *
                                    aDocumentCharsetInfo)
 {
     mDocumentCharsetInfo = aDocumentCharsetInfo;
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDocShell::GetChannelIsUnsafe(PRBool *aUnsafe)
+{
+    *aUnsafe = PR_FALSE;
+
+    nsCOMPtr<nsIChannel> channel;
+    GetCurrentDocumentChannel(getter_AddRefs(channel));
+    if (!channel) {
+        return NS_OK;
+    }
+
+    nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
+    if (!jarChannel) {
+        return NS_OK;
+    }
+
+    return jarChannel->GetIsUnsafe(aUnsafe);
+}
+
+NS_IMETHODIMP
 nsDocShell::GetAllowPlugins(PRBool * aAllowPlugins)
 {
     NS_ENSURE_ARG_POINTER(aAllowPlugins);
 
     *aAllowPlugins = mAllowPlugins;
+    if (!mAllowPlugins) {
+        return NS_OK;
+    }
+
+    PRBool unsafe;
+    *aAllowPlugins = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetAllowPlugins(PRBool aAllowPlugins)
 {
     mAllowPlugins = aAllowPlugins;
     //XXX should enable or disable a plugin host
@@ -1318,31 +1345,43 @@ nsDocShell::SetAllowPlugins(PRBool aAllo
 }
 
 NS_IMETHODIMP
 nsDocShell::GetAllowJavascript(PRBool * aAllowJavascript)
 {
     NS_ENSURE_ARG_POINTER(aAllowJavascript);
 
     *aAllowJavascript = mAllowJavascript;
+    if (!mAllowJavascript) {
+        return NS_OK;
+    }
+
+    PRBool unsafe;
+    *aAllowJavascript = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetAllowJavascript(PRBool aAllowJavascript)
 {
     mAllowJavascript = aAllowJavascript;
     return NS_OK;
 }
 
 NS_IMETHODIMP nsDocShell::GetAllowMetaRedirects(PRBool * aReturn)
 {
     NS_ENSURE_ARG_POINTER(aReturn);
 
     *aReturn = mAllowMetaRedirects;
+    if (!mAllowMetaRedirects) {
+        return NS_OK;
+    }
+
+    PRBool unsafe;
+    *aReturn = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
     return NS_OK;
 }
 
 NS_IMETHODIMP nsDocShell::SetAllowMetaRedirects(PRBool aValue)
 {
     mAllowMetaRedirects = aValue;
     return NS_OK;
 }
@@ -3031,16 +3070,20 @@ nsDocShell::DisplayLoadError(nsresult aE
         case NS_ERROR_PROXY_CONNECTION_REFUSED:
             // Proxy connection was refused.
             error.AssignLiteral("proxyConnectFailure");
             break;
         case NS_ERROR_INVALID_CONTENT_ENCODING:
             // Bad Content Encoding.
             error.AssignLiteral("contentEncodingError");
             break;
+        case NS_ERROR_UNSAFE_CONTENT_TYPE:
+            // Channel refused to load from an unrecognized content type.
+            error.AssignLiteral("unsafeContentType");
+            break;
         }
     }
 
     // Test if the error should be displayed
     if (error.IsEmpty()) {
         return NS_OK;
     }
 
@@ -6548,16 +6591,35 @@ nsDocShell::InternalLoad(nsIURI * aURI,
     //
     {
         PRBool inherits;
         // One more twist: Don't inherit the owner for external loads.
         if (aLoadType != LOAD_NORMAL_EXTERNAL && !owner &&
             (aFlags & INTERNAL_LOAD_FLAGS_INHERIT_OWNER) &&
             NS_SUCCEEDED(URIInheritsSecurityContext(aURI, &inherits)) &&
             inherits) {
+
+            // Don't allow loads that would inherit our security context
+            // if this document came from an unsafe channel.
+            nsCOMPtr<nsIDocShellTreeItem> treeItem = this;
+            do {
+                nsCOMPtr<nsIDocShell> itemDocShell =
+                    do_QueryInterface(treeItem);
+                PRBool isUnsafe;
+                if (itemDocShell &&
+                    NS_SUCCEEDED(itemDocShell->GetChannelIsUnsafe(&isUnsafe)) &&
+                    isUnsafe) {
+                    return NS_ERROR_DOM_SECURITY_ERR;
+                }
+
+                nsCOMPtr<nsIDocShellTreeItem> parent;
+                treeItem->GetSameTypeParent(getter_AddRefs(parent));
+                parent.swap(treeItem);
+            } while (treeItem);
+
             owner = GetInheritedPrincipal(PR_TRUE);
         }
     }
 
     //
     // Resolve the window target before going any further...
     // If the load has been targeted to another DocShell, then transfer the
     // load to it...
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -63,17 +63,17 @@ interface nsIWebNavigation;
 interface nsISimpleEnumerator;
 interface nsIInputStream;
 interface nsIRequest;
 interface nsISHEntry;
 interface nsILayoutHistoryState;
 interface nsISecureBrowserUI;
 interface nsIDOMStorage;
 
-[scriptable, uuid(10ed386d-8598-408c-b571-e75ad18edeb0)]
+[scriptable, uuid(4b00222a-8d0a-46d7-a1fe-43bd89d19324)]
 interface nsIDocShell : nsISupports
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -443,10 +443,17 @@ interface nsIDocShell : nsISupports
   [noscript] void setChildOffset(in unsigned long offset);
 
   /**
    * Find out whether the docshell is currently in the middle of a page
    * transition (after the onunload event has fired, but before the new
    * document has been set up)
    */
   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;
 };
 
--- a/docshell/base/nsWebShell.cpp
+++ b/docshell/base/nsWebShell.cpp
@@ -1191,16 +1191,17 @@ nsresult nsWebShell::EndPageLoad(nsIWebP
     // Errors to be shown for any frame
     else if (aStatus == NS_ERROR_NET_TIMEOUT ||
              aStatus == NS_ERROR_REDIRECT_LOOP ||
              aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE ||
              aStatus == NS_ERROR_NET_INTERRUPT ||
              aStatus == NS_ERROR_NET_RESET ||
              aStatus == NS_ERROR_MALWARE_URI ||
              aStatus == NS_ERROR_PHISHING_URI ||
+             aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
              NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
       DisplayLoadError(aStatus, url, nsnull, channel);
     }
     else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) {
       /* A document that was requested to be fetched *only* from
        * the cache is not in cache. May be this is one of those 
        * postdata results. Throw a  dialog to the user,
        * saying that the page has expired from cache and ask if 
--- a/docshell/resources/content/netError.xhtml
+++ b/docshell/resources/content/netError.xhtml
@@ -214,16 +214,17 @@
         <h1 id="et_unknownSocketType">&unknownSocketType.title;</h1>
         <h1 id="et_netReset">&netReset.title;</h1>
         <h1 id="et_netOffline">&netOffline.title;</h1>
         <h1 id="et_netInterrupt">&netInterrupt.title;</h1>
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
+        <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
@@ -235,16 +236,17 @@
         <div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div>
         <div id="ed_netReset">&netReset.longDesc;</div>
         <div id="ed_netOffline">&netOffline.longDesc;</div>
         <div id="ed_netInterrupt">&netInterrupt.longDesc;</div>
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
+        <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer">
--- a/docshell/test/Makefile.in
+++ b/docshell/test/Makefile.in
@@ -49,16 +49,18 @@ DIRS += chrome \
 
 XPCSHELL_TESTS = unit
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bug344861.html \
+		test_bug369814.html \
+		bug369814.zip       \
 		test_bug384014.html \
 		test_bug387979.html \
 		test_bug404548.html \
 		bug404548-subframe.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..86b9c8c0964e8012b94bc1d965b296aae512a6de
GIT binary patch
literal 1311
zc$}S-zmL-}6vtiCA`y{|6N1UgKso8!fH+aObFy(Aj-f(yMsDJ}q@;1M-Mb<&P*#|k
z85kJ(FSvi;b`TSP06TW=G)aFTge#}D{oePzzR!yX$5zt@YJ8m2-q$-9-;xFZYF-CB
z0B2!L8Slnok`A9=1J;}GsITU5)&##BHvm#60g4HZ0vkdk5|NU?xFJt3ZRBYKG@{XK
zX=l!Dh;q3?7$%YL3d~N4aAC~IiSI^O-~&Ns!c!6U)N;yF#6y<kA~;X7h@N+8PBOx}
zIc6ji-JHH8ciUQ~y^B0!>q5QdyQAspgNKhF-QPE%0;LM2EX-+=2@*jpj4|?XnOR2v
zBex={YHo#4HM?aJdoZKR=&EMASZEO>7%Vy6en)#dAI6tQlF^ajl$<a;Ar(&i2kNW2
ziIY=;GJXXpry%7RRMM1BM?5BJ%AEx+_0(RA(x~J+dhJ5a!fnNY+U6f>R8g#zNp2xe
z?*|)G8S{D~V|<3S_rYJnExp>iZZPKcD29Qv4x#rx>;3xq;>m^lt)_w?dxH@kmk@4!
zI_#q@FG~f2@hc(7lL?Vc5et!b$g62`=DWtoRi}D{RJLp;+xp(&@Yz#;rS2=07Bn}1
z*a+2XyMa3+QNl<l){EfSgX6}Po7T2l!vV$kJXg>99n#Ww$Z!TgRc5`ZOPROxFG{@D
j;w|0ct1|0Ysxlo5tUzjMNLr?KRhe1xb1A>~n`-q3F;<Bo
new file mode 100644
--- /dev/null
+++ b/docshell/test/test_bug369814.html
@@ -0,0 +1,204 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=369814
+-->
+<head>
+  <title>Test for Bug 369814</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384014">Mozilla Bug 369814</a>
+
+<p>
+
+<iframe id="testFrame"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Tests for Bug 369814 **/
+
+SimpleTest.waitForExplicitFinish();
+
+// Because child scripts won't be able to run to tell us they're done,
+// we need to just wait for them.  Wait this amount of time before checking
+// the results.
+const gLoadTimeout = 3000;
+
+var Ci = Components.interfaces;
+
+var gCurrentTest;
+var gTargetWindow;
+var gNumPokes;
+var gPrefValue;
+
+var gTestFrame = document.getElementById('testFrame');
+
+/**
+ * Called by documents loaded from jar files to indicate that they can access
+ * this document.
+ */
+function poke(description) {
+  ok(false, gCurrentTest['name'] + ": got unexpected poke: " + description);
+  gNumPokes++;
+}
+
+function loadEvent(window, callback)
+{
+  var fn = function() {
+    window.removeEventListener("load", fn, false);
+    callback();
+  };
+  window.addEventListener("load", fn, false);
+}
+
+function loadTestTarget(callback)
+{
+  gTargetWindow = window.open("http://localhost:8888", "bug369814target");
+  loadEvent(gTargetWindow, callback);
+}
+
+function closeTestTarget()
+{
+  gTargetWindow.close();
+  gTargetWindow = null;
+}
+
+function loadErrorTest(test)
+{
+  gTestFrame.src = test['url'];
+
+  // Give the frame a chance to fail at loading
+  setTimeout(function() {
+      // XXX: There doesn't seem to be a reliable check for "got an error,"
+      // but reaching in to an error document will throw an exception
+      var errorPage;
+      try {
+        var item = gTestFrame.contentDocument.getElementById(gCurrentTest['data-iframe']);
+        errorPage = false;
+      } catch (e) {
+        errorPage = true;
+      }
+      ok(errorPage, gCurrentTest["name"] + ": should block a suspicious JAR load.");
+
+      finishTest();
+    }, gLoadTimeout);
+}
+
+function iframeTest(test) {
+  gTestFrame.src = test['url'];
+  loadEvent(gTestFrame, function() {
+      finishTest();
+    });
+}
+
+function refreshTest(test) {
+  gTestFrame.src = test['url'];
+  loadEvent(gTestFrame, function() {
+      // Wait for the frame to try and refresh
+      // XXX: a "blocked redirect" signal would be needed to get rid of
+      // this timeout.
+      setTimeout(function() {
+          finishTest();
+        }, gLoadTimeout);
+    });
+}
+
+function anchorTest(test) {
+  loadTestTarget(function() {
+      gTestFrame.src = test['url'];
+      loadEvent(gTestFrame, function() {
+        sendMouseEvent({type:'click'}, 'target', gTestFrame.contentWindow);
+        sendMouseEvent({type:'click'}, 'notarget', gTestFrame.contentWindow);
+
+        // Give the clicks a chance to load
+        setTimeout(function() {
+            closeTestTarget();
+            finishTest();
+          }, gLoadTimeout);
+        });
+    });
+}
+
+var gTests = [
+  { "name" : "iframes.html loaded from non-jar type, pref disabled",
+    "url" : "jar:http://localhost:8888/tests/docshell/test/bug369814.zip!/iframes.html",
+    "pref" : false,
+    "pokes" : { },
+    "func" : loadErrorTest,
+  },
+  { "name" : "refresh.html loaded from non-jar type, pref enabled",
+    "url" : "jar:http://localhost:8888/tests/docshell/test/bug369814.zip!/refresh.html",
+    "pref" : true,
+    "pokes" : { },
+    "func" : refreshTest,
+  },
+  { "name" : "iframes.html loaded from non-jar type, pref enabled",
+    "url" : "jar:http://localhost:8888/tests/docshell/test/bug369814.zip!/iframes.html",
+    "pref" : true,
+    "pokes" : { },
+    "func" : iframeTest,
+  },
+  { "name" : "anchors.html loaded from non-jar type, pref enabled",
+    "url" : "jar:http://localhost:8888/tests/docshell/test/bug369814.zip!/anchors.html",
+    "pref" : true,
+    "pokes" : { },
+    "func" : anchorTest,
+  },
+];
+
+var gNextTest = 0;
+
+function runNextTest()
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+    getService(Components.interfaces.nsIPrefBranch);
+
+  if (gNextTest < gTests.length) {
+    gCurrentTest = gTests[gNextTest++];
+    gNumPokes = 0;
+
+    prefs.setBoolPref("network.jar.open-unsafe-types", gCurrentTest['pref']);
+
+    gCurrentTest['func'](gCurrentTest);
+  } else {
+    // Put back the pref value we had at test start
+    prefs.setBoolPref("network.jar.open-unsafe-types", gPrefValue);
+    SimpleTest.finish();
+  }
+}
+
+function finishTest()
+{
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+      getService(Components.interfaces.nsIPrefBranch);
+    prefs.setBoolPref("network.jar.open-unsafe-types", false);
+
+  if (gNumPokes == 0) {
+    ok(true, gCurrentTest["name"] + ": no unexpected pokes");
+  }
+
+  runNextTest();
+}
+
+function startTests()
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+    getService(Components.interfaces.nsIPrefBranch);
+  gPrefValue = prefs.getBoolPref("network.jar.open-unsafe-types");
+}
+
+addLoadEvent(runNextTest);
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/locales/en-US/chrome/appstrings.properties
+++ b/dom/locales/en-US/chrome/appstrings.properties
@@ -48,16 +48,17 @@ resendButton.label=Resend
 unknownSocketType=This document cannot be displayed unless you install the Personal Security Manager (PSM). Download and install PSM and try again, or contact your system administrator.
 netReset=The document contains no data.
 netOffline=This document cannot be displayed while offline. To go online, uncheck Work Offline from the File menu.
 isprinting=The document cannot change while Printing or in Print Preview.
 deniedPortAccess=Access to the port number given has been disabled for security reasons.
 proxyResolveFailure=The proxy server you have configured could not be found. Please check your proxy settings and try again.
 proxyConnectFailure=The connection was refused when attempting to contact the proxy server you have configured. Please check your proxy settings and try again.
 contentEncodingError=The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression. Please contact the website owners to inform them of this problem.
+unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 phishingBlocked=The web site at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
--- a/dom/locales/en-US/chrome/netError.dtd
+++ b/dom/locales/en-US/chrome/netError.dtd
@@ -25,16 +25,23 @@
 <!ENTITY netInterrupt.longDesc "<p>The browser connected successfully, but the connection was interrupted while transferring information.  Please try again.</p><ul><li>Are you unable to browse other sites? Check the computer's network connection.</li><li>Still having trouble? Consult your network administrator or Internet provider for assistance.</li></ul>">
 
 <!ENTITY netOffline.title "Offline Mode">
 <!ENTITY netOffline.longDesc "<p>The browser is operating in its offline mode and cannot connect to the requested item.</p><ul><li>Is the computer connected to an active network?</li><li>Place the browser in online mode and try again.</li></ul>">
 
 <!ENTITY contentEncodingError.title "Content Encoding Error">
 <!ENTITY contentEncodingError.longDesc "<p>The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
 
+<!ENTITY unsafeContentType.title "Unsafe File Type">
+<!ENTITY unsafeContentType.longDesc "
+<ul>
+  <li>Please contact the website owners to inform them of this problem.</li>
+</ul>
+">
+
 <!ENTITY netReset.title "Connection Interrupted">
 <!ENTITY netReset.longDesc "<p>The network link was interrupted while negotiating a connection. Please try again.</p>">
 
 <!ENTITY netTimeout.title "Network Timeout">
 <!ENTITY netTimeout.longDesc "<p>The requested site did not respond to a connection request and the browser has stopped waiting for a reply.</p><ul><li>Could the server be experiencing high demand or a temporary outage?  Try again later.</li><li>Are you unable to browse other sites? Check the computer's network connection.</li><li>Is your computer or network protected by a firewall or proxy?  Incorrect settings can interfere with Web browsing.</li><li>Still having trouble? Consult your network administrator or Internet provider for assistance.</li></ul>">
 
 <!ENTITY protocolNotFound.title "Unknown Protocol">
 <!ENTITY protocolNotFound.longDesc "<p>The address specifies a protocol (e.g. <q>wxyz://</q>) the browser does not recognize, so the browser cannot properly connect to the site.</p><ul><li>Are you trying to access multimedia or other non-text services? Check the site for extra requirements.</li><li>Some protocols may require third-party software or plugins before the browser can recognize them.</li></ul>">
--- a/modules/libjar/nsIJARChannel.idl
+++ b/modules/libjar/nsIJARChannel.idl
@@ -32,12 +32,19 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIChannel.idl"
 
-[scriptable, uuid(c7e410d1-85f2-11d3-9f63-006008a6efe9)]
+[scriptable, uuid(6e6cc56d-51eb-4299-a795-dcfd1229ab3d)]
 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.
+     */
+    readonly attribute boolean isUnsafe;
 };
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -39,16 +39,18 @@
 
 #include "nsJAR.h"
 #include "nsJARChannel.h"
 #include "nsJARProtocolHandler.h"
 #include "nsMimeTypes.h"
 #include "nsNetUtil.h"
 #include "nsInt64.h"
 #include "nsEscape.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsIFileURL.h"
 #include "nsIJAR.h"
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
@@ -217,16 +219,17 @@ nsJARInputThunk::IsNonBlocking(PRBool *n
 
 //-----------------------------------------------------------------------------
 
 nsJARChannel::nsJARChannel()
     : mContentLength(-1)
     , mLoadFlags(LOAD_NORMAL)
     , mStatus(NS_OK)
     , mIsPending(PR_FALSE)
+    , mIsUnsafe(PR_TRUE)
     , mJarInput(nsnull)
 {
 #if defined(PR_LOGGING)
     if (!gJarProtocolLog)
         gJarProtocolLog = PR_NewLogModule("nsJarProtocol");
 #endif
 
     // hold an owning reference to the jar handler
@@ -318,31 +321,33 @@ nsJARChannel::EnsureJarInput(PRBool bloc
     // try to get a nsIFile directly from the url, which will often succeed.
     {
         nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI);
         if (fileURL)
             fileURL->GetFile(getter_AddRefs(mJarFile));
     }
 
     if (mJarFile) {
+        mIsUnsafe = PR_FALSE;
+
         // NOTE: we do not need to deal with mSecurityInfo here,
         // because we're loading from a local file
         rv = CreateJarInput(gJarHandler->JarCache());
     }
     else if (blocking) {
         NS_NOTREACHED("need sync downloader");
         rv = NS_ERROR_NOT_IMPLEMENTED;
     }
     else {
         // kick off an async download of the base URI...
         rv = NS_NewDownloader(getter_AddRefs(mDownloader), this);
         if (NS_SUCCEEDED(rv))
             rv = NS_OpenURI(mDownloader, nsnull, mJarBaseURI, nsnull,
                             mLoadGroup, mCallbacks,
-                            mLoadFlags & ~LOAD_DOCUMENT_URI);
+                            mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS));
     }
     return rv;
 
 }
 
 //-----------------------------------------------------------------------------
 // nsIRequest
 //-----------------------------------------------------------------------------
@@ -639,16 +644,19 @@ nsJARChannel::SetContentLength(PRInt32 a
 NS_IMETHODIMP
 nsJARChannel::Open(nsIInputStream **stream)
 {
     LOG(("nsJARChannel::Open [this=%x]\n", this));
 
     NS_ENSURE_TRUE(!mJarInput, NS_ERROR_IN_PROGRESS);
     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
 
+    mJarFile = nsnull;
+    mIsUnsafe = PR_TRUE;
+
     nsresult rv = EnsureJarInput(PR_TRUE);
     if (NS_FAILED(rv)) return rv;
 
     if (!mJarInput)
         return NS_ERROR_UNEXPECTED;
 
     // force load the jar file now so GetContentLength will return a
     // meaningful value once we return.
@@ -661,16 +669,19 @@ nsJARChannel::Open(nsIInputStream **stre
 NS_IMETHODIMP
 nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
 {
     LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this));
 
     NS_ENSURE_ARG_POINTER(listener);
     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
 
+    mJarFile = nsnull;
+    mIsUnsafe = PR_TRUE;
+
     // Initialize mProgressSink
     NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
 
     nsresult rv = EnsureJarInput(PR_FALSE);
     if (NS_FAILED(rv)) return rv;
 
     if (mJarInput) {
         // create input stream pump
@@ -686,16 +697,26 @@ nsJARChannel::AsyncOpen(nsIStreamListene
 
     mListener = listener;
     mListenerContext = ctx;
     mIsPending = PR_TRUE;
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
+// nsIJARChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsJARChannel::GetIsUnsafe(PRBool *isUnsafe)
+{
+    *isUnsafe = mIsUnsafe;
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
 // nsIDownloadObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
                                  nsIRequest    *request,
                                  nsISupports   *context,
                                  nsresult       status,
@@ -724,32 +745,74 @@ nsJARChannel::OnDownloadComplete(nsIDown
                 rv = mJarURI->CloneWithJARFile(innerURI,
                                                getter_AddRefs(newURI));
                 if (NS_SUCCEEDED(rv)) {
                     mJarURI = newURI;
                 }
             }
             status = rv;
         }
+
+        nsCOMPtr<nsIHttpChannel> 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.
+            nsCAutoString header;
+            httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
+                                           header);
+
+            nsCAutoString contentType;
+            nsCAutoString charset;
+            NS_ParseContentType(header, contentType, charset);
+
+            mIsUnsafe = !contentType.EqualsLiteral("application/java-archive") &&
+                        !contentType.EqualsLiteral("application/x-jar");
+        } else {
+            nsCOMPtr<nsIJARChannel> innerJARChannel(do_QueryInterface(channel));
+            if (innerJARChannel) {
+                PRBool unsafe;
+                innerJARChannel->GetIsUnsafe(&unsafe);
+                mIsUnsafe = unsafe;
+            }
+        }
+
+        // XXX: THIS IS TEMPORARY
+        //mIsUnsafe = PR_FALSE;
+    }
+
+    if (mIsUnsafe) {
+        PRBool allowUnpack = PR_FALSE;
+
+        nsCOMPtr<nsIPrefBranch> prefs =
+            do_GetService(NS_PREFSERVICE_CONTRACTID);
+        if (prefs) {
+            prefs->GetBoolPref("network.jar.open-unsafe-types", &allowUnpack);
+        }
+
+        if (!allowUnpack) {
+            status = NS_ERROR_UNSAFE_CONTENT_TYPE;
+        }
     }
 
     if (NS_SUCCEEDED(status)) {
         mJarFile = file;
     
         rv = CreateJarInput(nsnull);
         if (NS_SUCCEEDED(rv)) {
             // create input stream pump
             rv = NS_NewInputStreamPump(getter_AddRefs(mPump), mJarInput);
             if (NS_SUCCEEDED(rv))
                 rv = mPump->AsyncRead(this, nsnull);
         }
         status = rv;
     }
 
     if (NS_FAILED(status)) {
+        mStatus = status;
         OnStartRequest(nsnull, nsnull);
         OnStopRequest(nsnull, nsnull, status);
     }
 
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -92,17 +92,18 @@ private:
     nsCOMPtr<nsILoadGroup>          mLoadGroup;
     nsCOMPtr<nsIStreamListener>     mListener;
     nsCOMPtr<nsISupports>           mListenerContext;
     nsCString                       mContentType;
     nsCString                       mContentCharset;
     PRInt32                         mContentLength;
     PRUint32                        mLoadFlags;
     nsresult                        mStatus;
-    PRBool                          mIsPending;
+    PRPackedBool                    mIsPending;
+    PRPackedBool                    mIsUnsafe;
 
     nsJARInputThunk                *mJarInput;
     nsCOMPtr<nsIStreamListener>     mDownloader;
     nsCOMPtr<nsIInputStreamPump>    mPump;
     nsCOMPtr<nsIFile>               mJarFile;
     nsCOMPtr<nsIURI>                mJarBaseURI;
     nsCString                       mJarEntry;
 };
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -608,16 +608,21 @@ pref("network.http.accept-encoding" ,"gz
 pref("network.http.pipelining"      , false);
 pref("network.http.proxy.pipelining", false);
 
 // Max number of requests in the pipeline
 pref("network.http.pipelining.maxrequests" , 4);
 
 // </http>
 
+// 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);
+
 // This preference controls whether or not internationalized domain names (IDN)
 // are handled.  IDN requires a nsIIDNService implementation.
 pref("network.enableIDN", 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.
 pref("network.IDN_show_punycode", false);
--- a/netwerk/base/public/nsNetError.h
+++ b/netwerk/base/public/nsNetError.h
@@ -227,16 +227,23 @@
     NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_NETWORK, 32)
 
 /**
  * The request failed as a result of a detected redirection loop.
  */
 #define NS_ERROR_REDIRECT_LOOP \
     NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_NETWORK, 31)
 
+/**
+ * The request failed because the content type returned by the server was
+ * not a type expected by the channel (for nested channels such as the JAR
+ * channel).
+ */
+#define NS_ERROR_UNSAFE_CONTENT_TYPE \
+    NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_NETWORK, 74)
 
 /******************************************************************************
  * FTP specific error codes:
  *
  * XXX document me
  */
 
 #define NS_ERROR_FTP_LOGIN \
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -10,30 +10,34 @@
 /**
  * Send a mouse event to the node with id aTarget. The "event" passed in to
  * aEvent is just a JavaScript object with the properties set that the real
  * mouse event object should have. This includes the type of the mouse event.
  * E.g. to send an click event to the node with id 'node' you might do this:
  *
  * sendMouseEvent({type:'click'}, 'node');
  */
-function sendMouseEvent(aEvent, aTarget) {
+function sendMouseEvent(aEvent, aTarget, aWindow) {
   if (['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
     throw new Error("sendMouseEvent doesn't know about event type '"+aEvent.type+"'");
   }
 
+  if (!aWindow) {
+    aWindow = window;
+  }
+
   // For events to trigger the UA's default actions they need to be "trusted"
   netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserWrite');
 
-  var event = document.createEvent('MouseEvent');
+  var event = aWindow.document.createEvent('MouseEvent');
 
   var typeArg          = aEvent.type;
   var canBubbleArg     = true;
   var cancelableArg    = true;
-  var viewArg          = window;
+  var viewArg          = aWindow;
   var detailArg        = aEvent.detail        || (aEvent.type == 'click'     ||
                                                   aEvent.type == 'mousedown' ||
                                                   aEvent.type == 'mouseup' ? 1 : 0);
   var screenXArg       = aEvent.screenX       || 0;
   var screenYArg       = aEvent.screenY       || 0;
   var clientXArg       = aEvent.clientX       || 0;
   var clientYArg       = aEvent.clientY       || 0;
   var ctrlKeyArg       = aEvent.ctrlKey       || false;
@@ -43,17 +47,17 @@ function sendMouseEvent(aEvent, aTarget)
   var buttonArg        = aEvent.button        || 0;
   var relatedTargetArg = aEvent.relatedTarget || null;
 
   event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
                        screenXArg, screenYArg, clientXArg, clientYArg,
                        ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
                        buttonArg, relatedTargetArg);
 
-  document.getElementById(aTarget).dispatchEvent(event);
+  aWindow.document.getElementById(aTarget).dispatchEvent(event);
 }
 
 /**
  * Send the char aChar to the node with id aTarget.  If aTarget is not
  * provided, use "target".  This method handles casing of chars (sends the
  * right charcode, and sends a shift key for uppercase chars).  No other
  * modifiers are handled at this point.
  *