Bug 466582 - smarter handling of remote chrome (and not allowing it)
authorShawn Wilsher <sdwilsh@shawnwilsher.com>
Tue, 09 Dec 2008 16:27:42 -0500
changeset 22559 242894260a86
parent 22558 99dca5692399
child 22560 29f1fa84f8fd
push id4067
push usersdwilsh@shawnwilsher.com
push date2008-12-09 21:28 +0000
treeherdermozilla-central@242894260a86 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs466582
milestone1.9.2a1pre
Bug 466582 - smarter handling of remote chrome (and not allowing it)
This adds a new flag, URI_IS_LOCAL_RESOURCE, to nsIProtocolHandler that allows
something of this protocol to be registered in chrome.

This changeset removes a runtime check every time we open a chrome channel to
see if it is remote to a registration time check that checks flags. The old
code could be easily worked around, and allowed for remote resources to be used.

r=bsmedberg
r=bz
sr=bz
chrome/src/nsChromeProtocolHandler.cpp
chrome/src/nsChromeRegistry.cpp
chrome/test/unit/data/test_bug401153.manifest
chrome/test/unit/data/test_data_protocol_registration.manifest
chrome/test/unit/data/test_no_remote_registration.manifest
chrome/test/unit/test_bug401153.js
chrome/test/unit/test_data_protocol_registration.js
chrome/test/unit/test_no_remote_registration.js
docshell/base/nsDocShell.cpp
modules/libpr0n/decoders/icon/nsIconProtocolHandler.cpp
netwerk/base/public/nsIProtocolHandler.idl
netwerk/protocol/data/src/nsDataHandler.cpp
netwerk/protocol/file/src/nsFileProtocolHandler.cpp
netwerk/protocol/res/src/nsResProtocolHandler.cpp
toolkit/components/places/src/nsAnnoProtocolHandler.cpp
--- a/chrome/src/nsChromeProtocolHandler.cpp
+++ b/chrome/src/nsChromeProtocolHandler.cpp
@@ -413,17 +413,17 @@ nsChromeProtocolHandler::AllowPort(PRInt
     // don't override anything.
     *_retval = PR_FALSE;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsChromeProtocolHandler::GetProtocolFlags(PRUint32 *result)
 {
-    *result = URI_STD | URI_IS_UI_RESOURCE;
+    *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsChromeProtocolHandler::NewURI(const nsACString &aSpec,
                                 const char *aCharset,
                                 nsIURI *aBaseURI,
                                 nsIURI **result)
@@ -460,17 +460,17 @@ nsChromeProtocolHandler::NewURI(const ns
 NS_IMETHODIMP
 nsChromeProtocolHandler::NewChannel(nsIURI* aURI,
                                     nsIChannel* *aResult)
 {
     nsresult rv;
 
     NS_ENSURE_ARG_POINTER(aURI);
     NS_PRECONDITION(aResult, "Null out param");
-    
+
 #ifdef DEBUG
     // Check that the uri we got is already canonified
     nsresult debug_rv;
     nsCOMPtr<nsIURI> debugClone;
     debug_rv = aURI->Clone(getter_AddRefs(debugClone));
     if (NS_SUCCEEDED(debug_rv)) {
         nsCOMPtr<nsIURL> debugURL (do_QueryInterface(debugClone));
         debug_rv = nsChromeRegistry::Canonify(debugURL);
@@ -549,48 +549,16 @@ nsChromeProtocolHandler::NewChannel(nsIU
         }
 
         nsCOMPtr<nsIIOService> ioServ (do_GetIOService(&rv));
         NS_ENSURE_SUCCESS(rv, rv);
 
         rv = ioServ->NewChannelFromURI(resolvedURI, getter_AddRefs(result));
         if (NS_FAILED(rv)) return rv;
 
-        // XXX Will be removed someday when we handle remote chrome.
-        nsCOMPtr<nsIFileChannel> fileChan
-            (do_QueryInterface(result));
-        if (fileChan) {
-#ifdef DEBUG
-            nsCOMPtr<nsIFile> file;
-            fileChan->GetFile(getter_AddRefs(file));
-
-            PRBool exists = PR_FALSE;
-            file->Exists(&exists);
-            if (!exists) {
-                nsCAutoString path;
-                file->GetNativePath(path);
-                printf("Chrome file doesn't exist: %s\n", path.get());
-            }
-#endif
-        }
-        else {
-            nsCOMPtr<nsIJARChannel> jarChan
-                (do_QueryInterface(result));
-            if (!jarChan) {
-                nsRefPtr<nsCachedChromeChannel> cachedChannel;
-                if (NS_FAILED(CallQueryInterface(result.get(),
-                        static_cast<nsCachedChromeChannel**>(
-                            getter_AddRefs(cachedChannel))))) {
-                    NS_WARNING("Remote chrome not allowed! Only file:, resource:, jar:, and cached chrome channels are valid.\n");
-                    result = nsnull;
-                    return NS_ERROR_FAILURE;
-                }
-            }
-        }
-
         // Make sure that the channel remembers where it was
         // originally loaded from.
         rv = result->SetOriginalURI(aURI);
         if (NS_FAILED(rv)) return rv;
 
         // Get a system principal for content files and set the owner
         // property of the result
         nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
@@ -622,34 +590,25 @@ nsChromeProtocolHandler::NewChannel(nsIU
         //       nsResURL class?
         nsCOMPtr<nsIFastLoadService> fastLoadServ(do_GetFastLoadService());
         if (fastLoadServ) {
             nsCOMPtr<nsIObjectOutputStream> objectOutput;
             fastLoadServ->GetOutputStream(getter_AddRefs(objectOutput));
             if (objectOutput) {
                 nsCOMPtr<nsIFile> file;
 
-                if (fileChan) {
-                    fileChan->GetFile(getter_AddRefs(file));
-                } else {
-                    nsCOMPtr<nsIURI> uri;
-                    result->GetURI(getter_AddRefs(uri));
+                nsCOMPtr<nsIURI> uri;
+                result->GetURI(getter_AddRefs(uri));
+                uri = NS_GetInnermostURI(uri);
 
-                    // Loop, jar URIs can nest (e.g. jar:jar:A.jar!B.jar!C.xml).
-                    // Often, however, we have jar:resource:/chrome/A.jar!C.xml.
-                    nsCOMPtr<nsIJARURI> jarURI;
-                    while ((jarURI = do_QueryInterface(uri)) != nsnull)
-                        jarURI->GetJARFile(getter_AddRefs(uri));
-
-                    // Here we have a URL of the form resource:/chrome/A.jar
-                    // or file:/some/path/to/A.jar.
-                    nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri));
-                    if (fileURL)
-                        fileURL->GetFile(getter_AddRefs(file));
-                }
+                // Here we have a URL of the form resource:/chrome/A.jar
+                // or file:/some/path/to/A.jar.
+                nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri));
+                if (fileURL)
+                    fileURL->GetFile(getter_AddRefs(file));
 
                 if (file) {
                     rv = fastLoadServ->AddDependency(file);
                     if (NS_FAILED(rv))
                         cache->AbortFastLoads();
                 }
             }
         }
--- a/chrome/src/nsChromeRegistry.cpp
+++ b/chrome/src/nsChromeRegistry.cpp
@@ -247,16 +247,26 @@ LanguagesMatch(const nsACString& a, cons
     // "b" is short
     if (bs == be)
       return (*as == '-');
   }
 
   return PR_FALSE;
 }
 
+static PRBool
+CanLoadResource(nsIURI* aResourceURI)
+{
+  PRBool isLocalResource = PR_FALSE;
+  (void)NS_URIChainHasFlags(aResourceURI,
+                            nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+                            &isLocalResource);
+  return isLocalResource;
+}
+
 nsChromeRegistry::ProviderEntry*
 nsChromeRegistry::nsProviderArray::GetProvider(const nsACString& aPreferred, MatchType aType)
 {
   PRInt32 i = mArray.Count();
   if (!i)
     return nsnull;
 
   ProviderEntry* found = nsnull;  // Only set if we find a partial-match locale
@@ -2329,16 +2339,23 @@ nsChromeRegistry::ProcessManifestBuffer(
         continue;
 
       nsCOMPtr<nsIURI> resolved;
       rv = io->NewURI(nsDependentCString(uri), nsnull, manifestURI,
                       getter_AddRefs(resolved));
       if (NS_FAILED(rv))
         continue;
 
+      if (!CanLoadResource(resolved)) {
+        LogMessageWithContext(resolved, line, nsIScriptError::warningFlag,
+                              "Warning: cannot register non-local URI '%s' as content.",
+                              uri);
+        continue;
+      }
+
       PackageEntry* entry =
         static_cast<PackageEntry*>(PL_DHashTableOperate(&mPackagesHash,
                                                             & (const nsACString&) nsDependentCString(package),
                                                             PL_DHASH_ADD));
       if (!entry)
         return NS_ERROR_OUT_OF_MEMORY;
 
       entry->baseURI = resolved;
@@ -2404,16 +2421,23 @@ nsChromeRegistry::ProcessManifestBuffer(
         continue;
 
       nsCOMPtr<nsIURI> resolved;
       rv = io->NewURI(nsDependentCString(uri), nsnull, manifestURI,
                       getter_AddRefs(resolved));
       if (NS_FAILED(rv))
         continue;
 
+      if (!CanLoadResource(resolved)) {
+        LogMessageWithContext(resolved, line, nsIScriptError::warningFlag,
+                              "Warning: cannot register non-local URI '%s' as a locale.",
+                              uri);
+        continue;
+      }
+
       PackageEntry* entry =
         static_cast<PackageEntry*>(PL_DHashTableOperate(&mPackagesHash,
                                                             & (const nsACString&) nsDependentCString(package),
                                                             PL_DHASH_ADD));
       if (!entry)
         return NS_ERROR_OUT_OF_MEMORY;
 
       entry->locales.SetBase(nsDependentCString(provider), resolved);
@@ -2459,16 +2483,23 @@ nsChromeRegistry::ProcessManifestBuffer(
         continue;
 
       nsCOMPtr<nsIURI> resolved;
       rv = io->NewURI(nsDependentCString(uri), nsnull, manifestURI,
                       getter_AddRefs(resolved));
       if (NS_FAILED(rv))
         continue;
 
+      if (!CanLoadResource(resolved)) {
+        LogMessageWithContext(resolved, line, nsIScriptError::warningFlag,
+                              "Warning: cannot register non-local URI '%s' as a skin.",
+                              uri);
+        continue;
+      }
+
       PackageEntry* entry =
         static_cast<PackageEntry*>(PL_DHashTableOperate(&mPackagesHash,
                                                             & (const nsACString&) nsDependentCString(package),
                                                             PL_DHASH_ADD));
       if (!entry)
         return NS_ERROR_OUT_OF_MEMORY;
 
       entry->skins.SetBase(nsDependentCString(provider), resolved);
@@ -2520,16 +2551,23 @@ nsChromeRegistry::ProcessManifestBuffer(
                        getter_AddRefs(baseuri));
       rv |= io->NewURI(nsDependentCString(overlay), nsnull, nsnull,
                        getter_AddRefs(overlayuri));
       if (NS_FAILED(rv)) {
         NS_WARNING("Could not make URIs for overlay directive. Ignoring.");
         continue;
       }
 
+      if (!CanLoadResource(overlayuri)) {
+        LogMessageWithContext(overlayuri, line, nsIScriptError::warningFlag,
+                              "Warning: cannot register non-local URI '%s' as an overlay.",
+                              overlay);
+        continue;
+      }
+
       mOverlayHash.Add(baseuri, overlayuri);
     }
     else if (!strcmp(token, "style")) {
       char *base    = nsCRT::strtok(whitespace, kWhitespace, &whitespace);
       char *overlay = nsCRT::strtok(whitespace, kWhitespace, &whitespace);
       if (!base || !overlay) {
         LogMessageWithContext(manifestURI, line, nsIScriptError::warningFlag,
                               "Warning: malformed chrome style instruction.");
@@ -2567,16 +2605,23 @@ nsChromeRegistry::ProcessManifestBuffer(
       nsCOMPtr<nsIURI> baseuri, overlayuri;
       rv  = io->NewURI(nsDependentCString(base), nsnull, nsnull,
                       getter_AddRefs(baseuri));
       rv |= io->NewURI(nsDependentCString(overlay), nsnull, nsnull,
                        getter_AddRefs(overlayuri));
       if (NS_FAILED(rv))
         continue;
 
+      if (!CanLoadResource(overlayuri)) {
+        LogMessageWithContext(overlayuri, line, nsIScriptError::warningFlag,
+                              "Warning: cannot register non-local URI '%s' as a style overlay.",
+                              overlay);
+        continue;
+      }
+
       mStyleHash.Add(baseuri, overlayuri);
     }
     else if (!strcmp(token, "override")) {
       if (aSkinOnly) {
         LogMessageWithContext(manifestURI, line, nsIScriptError::warningFlag,
                               "Warning: Ignoring override registration in skin-only manifest.");
         continue;
       }
@@ -2620,16 +2665,23 @@ nsChromeRegistry::ProcessManifestBuffer(
       nsCOMPtr<nsIURI> chromeuri, resolveduri;
       rv  = io->NewURI(nsDependentCString(chrome), nsnull, nsnull,
                       getter_AddRefs(chromeuri));
       rv |= io->NewURI(nsDependentCString(resolved), nsnull, manifestURI,
                        getter_AddRefs(resolveduri));
       if (NS_FAILED(rv))
         continue;
 
+      if (!CanLoadResource(resolveduri)) {
+        LogMessageWithContext(resolveduri, line, nsIScriptError::warningFlag,
+                              "Warning: cannot register non-local URI '%s' as an override.",
+                              resolved);
+        continue;
+      }
+
       mOverrideTable.Put(chromeuri, resolveduri);
     }
     else if (!strcmp(token, "resource")) {
       if (aSkinOnly) {
         LogMessageWithContext(manifestURI, line, nsIScriptError::warningFlag,
                               "Warning: Ignoring resource registration in skin-only manifest.");
         continue;
       }
@@ -2685,16 +2737,23 @@ nsChromeRegistry::ProcessManifestBuffer(
       }
 
       nsCOMPtr<nsIURI> resolved;
       rv = io->NewURI(nsDependentCString(uri), nsnull, manifestURI,
                       getter_AddRefs(resolved));
       if (NS_FAILED(rv))
         continue;
 
+      if (!CanLoadResource(resolved)) {
+        LogMessageWithContext(resolved, line, nsIScriptError::warningFlag,
+                              "Warning: cannot register non-local URI '%s' as a resource.",
+                              uri);
+        continue;
+      }
+
       rv = rph->SetSubstitution(host, resolved);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     else {
       LogMessageWithContext(manifestURI, line, nsIScriptError::warningFlag,
                             "Warning: Ignoring unrecognized chrome manifest instruction.");
     }
   }
--- a/chrome/test/unit/data/test_bug401153.manifest
+++ b/chrome/test/unit/data/test_bug401153.manifest
@@ -1,17 +1,14 @@
 # Should work
 resource test1 test1/
 
 # Duplicates should be ignored
 resource test1 foo/
 
-# Mapping off file system should work
-resource test2 http://www.mozilla.org/
-
 # Mapping into jar files should work
 resource test3 jar:test3.jar!/resources/
 
 # Invalid line
 resource test4
 
 # Check we made it through the whole manifest
 resource test5 test5/
new file mode 100644
--- /dev/null
+++ b/chrome/test/unit/data/test_data_protocol_registration.manifest
@@ -0,0 +1,5 @@
+# package used only for valid override
+content good-package bar/
+
+# Local resource (should work)
+override chrome://good-package/content/test.xul data:application/vnd.mozilla.xul+xml,
new file mode 100644
--- /dev/null
+++ b/chrome/test/unit/data/test_no_remote_registration.manifest
@@ -0,0 +1,32 @@
+# package used only for valid overlaying and overrides
+content good-package bar/
+
+# UI Resource URIs (should not work)
+content moz-protocol-ui-resource moz-protocol-ui-resource://foo/
+locale moz-protocol-ui-resource en-us moz-protocol-ui-resource://foo/
+skin moz-protocol-ui-resource skin1 moz-protocol-ui-resource://foo/
+override chrome://good-package/content/override-moz-protocol-ui-resource.xul moz-protocol-ui-resource://foo/
+resource moz-protocol-ui-resource moz-protocol-ui-resource://foo/
+
+# Local file URIs (should not work)
+content moz-protocol-local-file moz-protocol-local-file://foo/
+locale moz-protocol-local-file en-us moz-protocol-local-file://foo/
+skin moz-protocol-local-file skin1 moz-protocol-local-file://foo/
+override chrome://good-package/content/override-moz-protocol-local-file.xul moz-protocol-local-file://foo/
+resource moz-protocol-local-file moz-protocol-local-file://foo/
+
+# Loadable by anyone URIs (should not work)
+content moz-protocol-loadable-by-anyone moz-protocol-loadable-by-anyone://foo/
+locale moz-protocol-loadable-by-anyone en-us moz-protocol-loadable-by-anyone://foo/
+skin moz-protocol-loadable-by-anyone skin1 moz-protocol-loadable-by-anyone://foo/
+override chrome://good-package/content/override-moz-protocol-loadable-by-anyone.xul moz-protocol-loadable-by-anyone://foo/
+resource moz-protocol-loadable-by-anyone moz-protocol-loadable-by-anyone://foo/
+
+# Working protocols should be after this point.  Failing ones should be before.
+
+# Local resource (should work)
+content moz-protocol-local-resource moz-protocol-local-resource://foo/
+locale moz-protocol-local-resource en-us moz-protocol-local-resource://foo/
+skin moz-protocol-local-resource skin1 moz-protocol-local-resource://foo/
+override chrome://good-package/content/override-moz-protocol-local-resource.xul moz-protocol-local-resource://foo/
+resource moz-protocol-local-resource moz-protocol-local-resource://foo/
--- a/chrome/test/unit/test_bug401153.js
+++ b/chrome/test/unit/test_bug401153.js
@@ -84,32 +84,31 @@ chromeReg.checkForNewChrome();
 
 var rph = gIOS.getProtocolHandler("resource")
               .QueryInterface(Ci.nsIResProtocolHandler);
 
 function test_succeeded_mapping(namespace, target)
 {
   try {
     do_check_true(rph.hasSubstitution(namespace));
-    var thistarget = target + "/test.js";
-    var uri = gIOS.newURI("resource://" + namespace + "/test.js",
-                          null, null);
-    do_check_eq(rph.resolveURI(uri), thistarget);
+    var uri = gIOS.newURI("resource://" + namespace, null, null);
+    dump("### checking for " + target + ", getting " + rph.resolveURI(uri) + "\n");
+    do_check_eq(rph.resolveURI(uri), target);
   }
   catch (ex) {
+    dump(ex + "\n");
     do_throw(namespace);
   }
 }
 
 function test_failed_mapping(namespace)
 {
   do_check_false(rph.hasSubstitution(namespace));
 }
 
 function run_test()
 {
   var data = gIOS.newFileURI(do_get_file("chrome/test/unit/data")).spec;
-  test_succeeded_mapping("test1", data + "test1");
-  test_succeeded_mapping("test2", "http://www.mozilla.org");
-  test_succeeded_mapping("test3", "jar:" + data + "test3.jar!/resources");
+  test_succeeded_mapping("test1", data + "test1/");
+  test_succeeded_mapping("test3", "jar:" + data + "test3.jar!/resources/");
   test_failed_mapping("test4");
-  test_succeeded_mapping("test5", data + "test5");
+  test_succeeded_mapping("test5", data + "test5/");
 }
new file mode 100644
--- /dev/null
+++ b/chrome/test/unit/test_data_protocol_registration.js
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 tw=78 expandtab :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Chrome Registration Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+let manifests = [
+  do_get_file("chrome/test/unit/data/test_data_protocol_registration.manifest"),
+];
+registerManifests(manifests);
+
+let XULAppInfoFactory = {
+  // These two are used when we register all our factories (and unregister)
+  CID: XULAPPINFO_CID,
+  scheme: "XULAppInfo",
+  contractID: XULAPPINFO_CONTRACTID,
+  createInstance: function (outer, iid) {
+    if (outer != null)
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    return XULAppInfo.QueryInterface(iid);
+  }
+};
+
+function run_test()
+{
+  // Add our XULAppInfo factory
+  let factories = [XULAppInfoFactory];
+
+  // Register our factories
+  for (let i = 0; i < factories.length; i++) {
+    let factory = factories[i];
+    Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+              .registerFactory(factory.CID, "test-" + factory.scheme,
+                               factory.contractID, factory);
+  }
+
+  // Check for new chrome
+  let cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
+           getService(Ci.nsIChromeRegistry);
+  cr.checkForNewChrome();
+
+  // Check that our override worked
+  let expectedURI = "data:application/vnd.mozilla.xul+xml,";
+  let sourceURI = "chrome://good-package/content/test.xul";
+  try {
+    let ios = Cc["@mozilla.org/network/io-service;1"].
+              getService(Ci.nsIIOService);
+    sourceURI = ios.newURI(sourceURI, null, null);
+    // this throws for packages that are not registered
+    let uri = cr.convertChromeURL(sourceURI).spec;
+
+    do_check_eq(expectedURI, uri);
+  }
+  catch (e) {
+    dump(e + "\n");
+    do_throw("Should have registered our URI!");
+  }
+
+  // Unregister our factories so we do not leak
+  for (let i = 0; i < factories.length; i++) {
+    let factory = factories[i];
+    Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+              .unregisterFactory(factory.CID, factory);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/chrome/test/unit/test_no_remote_registration.js
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 tw=78 expandtab :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Chrome Registration Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * 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 ***** */
+
+let manifests = [
+  do_get_file("chrome/test/unit/data/test_no_remote_registration.manifest"),
+];
+registerManifests(manifests);
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let XULAppInfo = {
+  vendor: "Mozilla",
+  name: "XPCShell",
+  ID: "{39885e5f-f6b4-4e2a-87e5-6259ecf79011}",
+  version: "5",
+  appBuildID: "2007010101",
+  platformVersion: "1.9",
+  platformBuildID: "2007010101",
+  inSafeMode: false,
+  logConsoleErrors: true,
+  OS: "XPCShell",
+  XPCOMABI: "noarch-spidermonkey",
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIXULAppInfo,
+    Ci.nsIXULRuntime,
+  ])
+};
+
+let XULAppInfoFactory = {
+  // These two are used when we register all our factories (and unregister)
+  CID: XULAPPINFO_CID,
+  scheme: "XULAppInfo",
+  contractID: XULAPPINFO_CONTRACTID,
+  createInstance: function (outer, iid) {
+    if (outer != null)
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    return XULAppInfo.QueryInterface(iid);
+  }
+};
+
+function ProtocolHandler(aScheme, aFlags)
+{
+  this.scheme = aScheme;
+  this.protocolFlags = aFlags;
+  this.contractID = "@mozilla.org/network/protocol;1?name=" + aScheme;
+}
+
+ProtocolHandler.prototype =
+{
+  defaultPort: -1,
+  allowPort: function() false,
+  newURI: function(aSpec, aCharset, aBaseURI)
+  {
+    let uri = Cc["@mozilla.org/network/standard-url;1"].
+              createInstance(Ci.nsIURI);
+    uri.spec = aSpec;
+    if (!uri.scheme) {
+      // We got a partial uri, so let's resolve it with the base one
+      uri.spec = aBaseURI.resolve(aSpec);
+    }
+    return uri;
+  },
+  newChannel: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED },
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIProtocolHandler
+  ])
+};
+
+let testProtocols = [
+  // It doesn't matter if it has this flag - the only flag we accept is
+  // URI_IS_LOCAL_RESOURCE.
+  {scheme: "moz-protocol-ui-resource",
+   flags: Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE,
+   CID: Components.ID("{d6dedc93-558f-44fe-90f4-3b4bba8a0b14}"),
+   shouldRegister: false
+  },
+  // It doesn't matter if it has this flag - the only flag we accept is
+  // URI_IS_LOCAL_RESOURCE.
+  {scheme: "moz-protocol-local-file",
+   flags: Ci.nsIProtocolHandler.URI_IS_LOCAL_FILE,
+   CID: Components.ID("{ee30d594-0a2d-4f47-89cc-d4cde320ab69}"),
+   shouldRegister: false
+  },
+  // This clearly is non-local
+  {scheme: "moz-protocol-loadable-by-anyone",
+   flags: Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE,
+   CID: Components.ID("{c3735f23-3b0c-4a33-bfa0-79436dcd40b2}"),
+   shouldRegister: false
+  },
+  // This should always be last (unless we add more flags that are OK)
+  {scheme: "moz-protocol-local-resource",
+   flags: Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
+   CID: Components.ID("{b79e977c-f840-469a-b413-0125cc1b62a5}"),
+   shouldRegister: true
+  },
+];
+function run_test()
+{
+  // Create factories
+  let factories = [];
+  for (let i = 0; i < testProtocols.length; i++) {
+    factories[i] = {
+      scheme: testProtocols[i].scheme,
+      flags: testProtocols[i].flags,
+      CID: testProtocols[i].CID,
+      contractID: "@mozilla.org/network/protocol;1?name=" + testProtocols[i].scheme,
+      createInstance: function(aOuter, aIID)
+      {
+        if (aOuter != null)
+          throw Cr.NS_ERROR_NO_AGGREGATION;
+        let handler = new ProtocolHandler(this.scheme, this.flags, this.CID);
+        return handler.QueryInterface(aIID);
+      }
+    };
+  }
+  // Add our XULAppInfo factory
+  factories.push(XULAppInfoFactory);
+
+  // Register our factories
+  for (let i = 0; i < factories.length; i++) {
+    let factory = factories[i];
+    Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+              .registerFactory(factory.CID, "test-" + factory.scheme,
+                               factory.contractID, factory);
+  }
+
+  // Check for new chrome
+  let cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
+           getService(Ci.nsIChromeRegistry);
+  cr.checkForNewChrome();
+
+  // See if our various things were able to register
+  let registrationTypes = [
+    "content",
+    "locale",
+    "skin",
+    "override",
+    "resource",
+  ];
+  for (let i = 0; i < testProtocols.length; i++) {
+    let protocol = testProtocols[i];
+    for (let j = 0; j < registrationTypes.length; j++) {
+      let type = registrationTypes[j];
+      dump("Testing protocol '" + protocol.scheme + "' with type '" + type +
+           "'\n");
+      let expectedURI = protocol.scheme + "://foo/";
+      let sourceURI = "chrome://" + protocol.scheme + "/" + type + "/";
+      switch (type) {
+        case "content":
+          expectedURI += protocol.scheme + ".xul";
+          break;
+        case "locale":
+          expectedURI += protocol.scheme + ".dtd";
+          break;
+        case "skin":
+          expectedURI += protocol.scheme + ".css";
+          break;
+        case "override":
+          sourceURI = "chrome://good-package/content/override-" +
+                      protocol.scheme + ".xul";
+          break;
+        case "resource":
+          sourceURI = "resource://" + protocol.scheme + "/";
+          break;
+      };
+      try {
+        let ios = Cc["@mozilla.org/network/io-service;1"].
+                  getService(Ci.nsIIOService);
+        sourceURI = ios.newURI(sourceURI, null, null);
+        let uri;
+        if (type == "resource") {
+          // resources go about a slightly different way than everything else
+          let rph = ios.getProtocolHandler("resource").
+                    QueryInterface(Ci.nsIResProtocolHandler);
+          // this throws for packages that are not registered
+          uri = rph.resolveURI(sourceURI);
+        }
+        else {
+          // this throws for packages that are not registered
+          uri = cr.convertChromeURL(sourceURI).spec;
+        }
+
+        if (protocol.shouldRegister) {
+          do_check_eq(expectedURI, uri);
+        }
+        else {
+          // Overrides will not throw, so we'll get to here.  We want to make
+          // sure that the two strings are not the same in this situation.
+          do_check_neq(expectedURI, uri);
+        }
+      }
+      catch (e) {
+        if (protocol.shouldRegister) {
+          dump(e + "\n");
+          do_throw("Should have registered our URI for protocol " +
+                   protocol.scheme);
+        }
+      }
+    }
+  }
+
+  // Unregister our factories so we do not leak
+  for (let i = 0; i < factories.length; i++) {
+    let factory = factories[i];
+    Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+              .unregisterFactory(factory.CID, factory);
+  }
+}
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9700,16 +9700,22 @@ nsClassifierCallback::Run()
     if (hasFlags) return NS_OK;
 
     rv = NS_URIChainHasFlags(uri,
                              nsIProtocolHandler::URI_IS_UI_RESOURCE,
                              &hasFlags);
     NS_ENSURE_SUCCESS(rv, rv);
     if (hasFlags) return NS_OK;
 
+    rv = NS_URIChainHasFlags(uri,
+                             nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+                             &hasFlags);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (hasFlags) return NS_OK;
+
     nsCOMPtr<nsIURIClassifier> uriClassifier =
         do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
     if (NS_FAILED(rv)) return rv;
 
     PRBool expectCallback;
     rv = uriClassifier->Classify(uri, this, &expectCallback);
     if (NS_FAILED(rv)) return rv;
 
--- a/modules/libpr0n/decoders/icon/nsIconProtocolHandler.cpp
+++ b/modules/libpr0n/decoders/icon/nsIconProtocolHandler.cpp
@@ -76,19 +76,20 @@ NS_IMETHODIMP nsIconProtocolHandler::Get
 
 NS_IMETHODIMP nsIconProtocolHandler::AllowPort(PRInt32 port, const char *scheme, PRBool *_retval)
 {
     // don't override anything.  
     *_retval = PR_FALSE;
     return NS_OK;
 }
 
-NS_IMETHODIMP nsIconProtocolHandler::GetProtocolFlags(PRUint32 *result) 
+NS_IMETHODIMP nsIconProtocolHandler::GetProtocolFlags(PRUint32 *result)
 {
-  *result = URI_NORELATIVE | URI_NOAUTH | URI_IS_UI_RESOURCE;
+  *result = URI_NORELATIVE | URI_NOAUTH | URI_IS_UI_RESOURCE |
+            URI_IS_LOCAL_RESOURCE;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsIconProtocolHandler::NewURI(const nsACString &aSpec,
                                             const char *aOriginCharset, // ignored
                                             nsIURI *aBaseURI,
                                             nsIURI **result) 
 {
--- a/netwerk/base/public/nsIProtocolHandler.idl
+++ b/netwerk/base/public/nsIProtocolHandler.idl
@@ -216,17 +216,24 @@ interface nsIProtocolHandler : nsISuppor
     const unsigned long URI_NON_PERSISTABLE = (1<<10);
 
     /**
      * Channels using this protocol never call OnDataAvailable
      * on the listener passed to AsyncOpen and they therefore
      * do not return any data that we can use.
      */
     const unsigned long URI_DOES_NOT_RETURN_DATA = (1<<11);
-    
+
+    /**
+     * URIs for this protocol are considered to be local resources.  This could
+     * be a local file (URI_IS_LOCAL_FILE), a UI resource (URI_IS_UI_RESOURCE),
+     * or something else that would not hit the network.
+     */
+    const unsigned long URI_IS_LOCAL_RESOURCE = (1<<12);
+
     /**
      * This protocol handler can be proxied via a proxy (socks or http)
      * (e.g., irc, smtp, http, etc.).  If the protocol supports transparent
      * proxying, the handler should implement nsIProxiedProtocolHandler.
      *
      * If it supports only HTTP proxying, then it need not support
      * nsIProxiedProtocolHandler, but should instead set the ALLOWS_PROXY_HTTP
      * flag (see below).
--- a/netwerk/protocol/data/src/nsDataHandler.cpp
+++ b/netwerk/protocol/data/src/nsDataHandler.cpp
@@ -86,17 +86,17 @@ nsDataHandler::GetDefaultPort(PRInt32 *r
     // no ports for data protocol
     *result = -1;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDataHandler::GetProtocolFlags(PRUint32 *result) {
     *result = URI_NORELATIVE | URI_NOAUTH | URI_INHERITS_SECURITY_CONTEXT |
-        URI_LOADABLE_BY_ANYONE | URI_NON_PERSISTABLE;
+        URI_LOADABLE_BY_ANYONE | URI_NON_PERSISTABLE | URI_IS_LOCAL_RESOURCE;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDataHandler::NewURI(const nsACString &aSpec,
                       const char *aCharset, // ignore charset info
                       nsIURI *aBaseURI,
                       nsIURI **result) {
--- a/netwerk/protocol/file/src/nsFileProtocolHandler.cpp
+++ b/netwerk/protocol/file/src/nsFileProtocolHandler.cpp
@@ -246,17 +246,17 @@ nsFileProtocolHandler::GetDefaultPort(PR
 {
     *result = -1;        // no port for file: URLs
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFileProtocolHandler::GetProtocolFlags(PRUint32 *result)
 {
-    *result = URI_NOAUTH | URI_IS_LOCAL_FILE;
+    *result = URI_NOAUTH | URI_IS_LOCAL_FILE | URI_IS_LOCAL_RESOURCE;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFileProtocolHandler::NewURI(const nsACString &spec,
                               const char *charset,
                               nsIURI *baseURI,
                               nsIURI **result)
--- a/netwerk/protocol/res/src/nsResProtocolHandler.cpp
+++ b/netwerk/protocol/res/src/nsResProtocolHandler.cpp
@@ -210,17 +210,17 @@ nsResProtocolHandler::GetDefaultPort(PRI
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsResProtocolHandler::GetProtocolFlags(PRUint32 *result)
 {
     // XXXbz Is this really true for all resource: URIs?  Could we
     // somehow give different flags to some of them?
-    *result = URI_STD | URI_IS_UI_RESOURCE;
+    *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsResProtocolHandler::NewURI(const nsACString &aSpec,
                              const char *aCharset,
                              nsIURI *aBaseURI,
                              nsIURI **result)
--- a/toolkit/components/places/src/nsAnnoProtocolHandler.cpp
+++ b/toolkit/components/places/src/nsAnnoProtocolHandler.cpp
@@ -82,17 +82,18 @@ nsAnnoProtocolHandler::GetDefaultPort(PR
 }
 
 
 // nsAnnoProtocolHandler::GetProtocolFlags
 
 NS_IMETHODIMP
 nsAnnoProtocolHandler::GetProtocolFlags(PRUint32 *aProtocolFlags)
 {
-  *aProtocolFlags = (URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD);
+  *aProtocolFlags = (URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+                     URI_IS_LOCAL_RESOURCE);
   return NS_OK;
 }
 
 
 // nsAnnoProtocolHandler::NewURI
 
 NS_IMETHODIMP
 nsAnnoProtocolHandler::NewURI(const nsACString& aSpec,