Bug 718203: don't allow drops of javascript: URIs on the home button, r=enndeakin, sr=bz, a=akeybl
authorGavin Sharp <gavin@gavinsharp.com>
Mon, 30 Jan 2012 17:58:30 -0800
changeset 87219 6ff957842178637b56f1303907cb97b77f3d607b
parent 87218 c98c403449370a31b17e179a9f3fe3d5ce0f7393
child 87220 b740b95280281e90cdd5d8941b675581cff066b3
push id879
push usergsharp@mozilla.com
push dateTue, 21 Feb 2012 18:20:25 +0000
treeherdermozilla-aurora@b740b9528028 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersenndeakin, bz, akeybl
bugs718203
milestone12.0a2
Bug 718203: don't allow drops of javascript: URIs on the home button, r=enndeakin, sr=bz, a=akeybl
browser/base/content/browser.js
browser/base/content/test/Makefile.in
browser/base/content/test/browser_homeDrop.js
browser/base/content/test/browser_locationBarCommand.js
content/base/public/nsIDroppedLinkHandler.idl
content/base/src/contentAreaDropListener.js
embedding/browser/webBrowser/nsDocShellTreeOwner.cpp
testing/mochitest/tests/SimpleTest/ChromeUtils.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3131,23 +3131,27 @@ var browserDragAndDrop = {
 
   dragOver: function (aEvent)
   {
     if (this.canDropLink(aEvent)) {
       aEvent.preventDefault();
     }
   },
 
-  drop: function (aEvent, aName) Services.droppedLinkHandler.dropLink(aEvent, aName)
+  drop: function (aEvent, aName, aDisallowInherit) {
+    return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit);
+  }
 };
 
 var homeButtonObserver = {
   onDrop: function (aEvent)
     {
-      setTimeout(openHomeDialog, 0, browserDragAndDrop.drop(aEvent, { }));
+      // disallow setting home pages that inherit the principal
+      let url = browserDragAndDrop.drop(aEvent, {}, true);
+      setTimeout(openHomeDialog, 0, url);
     },
 
   onDragOver: function (aEvent)
     {
       browserDragAndDrop.dragOver(aEvent);
       aEvent.dropEffect = "link";
     },
   onDragExit: function (aEvent)
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -177,16 +177,17 @@ endif
                  browser_bug624734.js \
                  browser_bug647886.js \
                  browser_bug655584.js \
                  browser_bug664672.js \
                  browser_bug710878.js \
                  browser_bug719271.js \
                  browser_canonizeURL.js \
                  browser_findbarClose.js \
+                 browser_homeDrop.js \
                  browser_keywordBookmarklets.js \
                  browser_contextSearchTabPosition.js \
                  browser_ctrlTab.js \
                  browser_customize_popupNotification.js \
                  browser_disablechrome.js \
                  browser_discovery.js \
                  browser_duplicateIDs.js \
                  browser_gestureSupport.js \
@@ -258,17 +259,17 @@ endif
                  browser_addon_bar_close_button.js \
                  browser_addon_bar_shortcut.js \
                  browser_addon_bar_aomlistener.js \
                  test_bug628179.html \
                  browser_wyciwyg_urlbarCopying.js \
                  test_wyciwyg_copying.html \
                  authenticate.sjs \
                  browser_minimize.js \
-								 browser_aboutSyncProgress.js \
+                 browser_aboutSyncProgress.js \
                  browser_middleMouse_inherit.js \
                  redirect_bug623155.sjs \
                  $(NULL)
 
 ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 _BROWSER_FILES += \
 		browser_bug462289.js \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_homeDrop.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  waitForExplicitFinish();
+
+  // Open a new tab, since starting a drag from the home button activates it and
+  // we don't want to interfere with future tests by loading the home page.
+  let newTab = gBrowser.selectedTab = gBrowser.addTab();
+  registerCleanupFunction(function () {
+    gBrowser.removeTab(newTab);
+  });
+
+  let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+                     getService(Ci.mozIJSSubScriptLoader);
+  let chromeUtils = {};
+  scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", chromeUtils);
+
+  let homeButton = document.getElementById("home-button");
+  ok(homeButton, "home button present");
+
+  let dialogListener = new WindowListener("chrome://global/content/commonDialog.xul", function (domwindow) {
+    ok(true, "dialog appeared in response to home button drop");
+    domwindow.document.documentElement.cancelDialog();
+    Services.wm.removeListener(dialogListener);
+
+    // Now trigger the invalid URI test
+    executeSoon(function () {
+      let consoleListener = {
+        observe: function (m) {
+          if (m.message.indexOf("NS_ERROR_DOM_BAD_URI") > -1) {
+            Services.console.unregisterListener(consoleListener);
+            ok(true, "drop was blocked");
+            executeSoon(finish);
+          }
+        }
+      }
+      Services.console.registerListener(consoleListener);
+
+      // The drop handler throws an exception when dragging URIs that inherit
+      // principal, e.g. javascript:
+      expectUncaughtException();
+      chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window, EventUtils);
+    })
+  });
+
+  Services.wm.addListener(dialogListener);
+
+  chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "http://mochi.test:8888/"}]], "copy", window, EventUtils);
+}
+
+function WindowListener(aURL, aCallback) {
+  this.callback = aCallback;
+  this.url = aURL;
+}
+WindowListener.prototype = {
+  onOpenWindow: function(aXULWindow) {
+    var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDOMWindow);
+    var self = this;
+    domwindow.addEventListener("load", function() {
+      domwindow.removeEventListener("load", arguments.callee, false);
+
+      ok(true, "domwindow.document.location.href: " + domwindow.document.location.href);
+      if (domwindow.document.location.href != self.url)
+        return;
+
+      // Allow other window load listeners to execute before passing to callback
+      executeSoon(function() {
+        self.callback(domwindow);
+      });
+    }, false);
+  },
+  onCloseWindow: function(aXULWindow) {},
+  onWindowTitleChange: function(aXULWindow, aNewTitle) {}
+}
+
--- a/browser/base/content/test/browser_locationBarCommand.js
+++ b/browser/base/content/test/browser_locationBarCommand.js
@@ -125,18 +125,21 @@ let gTests = [
   }
 ]
 
 let gGoButton = document.getElementById("urlbar-go-button");
 function triggerCommand(aClick, aEvent) {
   gURLBar.value = TEST_VALUE;
   gURLBar.focus();
 
-  if (aClick)
+  if (aClick) {
+    is(gURLBar.getAttribute("pageproxystate"), "invalid",
+       "page proxy state must be invalid for go button to be visible");
     EventUtils.synthesizeMouseAtCenter(gGoButton, aEvent); 
+  }
   else
     EventUtils.synthesizeKey("VK_RETURN", aEvent);
 }
 
 /* Checks that the URL was loaded in the current tab */
 function checkCurrent(aTab) {
   info("URL should be loaded in the current tab");
   is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
--- a/content/base/public/nsIDroppedLinkHandler.idl
+++ b/content/base/public/nsIDroppedLinkHandler.idl
@@ -34,37 +34,42 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIDOMDragEvent;
 interface nsIURI;
 
-[scriptable, uuid(F266B79B-7026-4D2D-B4BD-4F2C6B6C59B4)]
+[scriptable, uuid(6B58A5A7-76D0-4E93-AB2E-4DE108683FF8)]
 interface nsIDroppedLinkHandler : nsISupports
 {
   /**
    * Determines if a link being dragged can be dropped and returns true if so.
    * aEvent should be a dragenter or dragover event.
    *
    * If aAllowSameDocument is false, drops are only allowed if the document
    * of the source of the drag is different from the destination. This check
    * includes any parent, sibling and child frames in the same content tree.
    * If true, the source is not checked.
    */
   boolean canDropLink(in nsIDOMDragEvent aEvent, in boolean aAllowSameDocument);
 
   /**
    * Given a drop event aEvent, determines the link being dragged and returns
    * it. If a uri is returned the caller can, for instance, load it. If null
-   * is returned, there is no valid link to be dropped. A
-   * NS_ERROR_DOM_SECURITY_ERR error will be thrown and the event cancelled if
+   * is returned, there is no valid link to be dropped.
+   *
+   * A NS_ERROR_DOM_SECURITY_ERR error will be thrown and the event cancelled if
    * the receiving target should not load the uri for security reasons. This
-   * will occur if the source of the drag initiated a link for dragging that
-   * it itself cannot access. This prevents a source document from tricking
-   * the user into a dragging a chrome url for example.
+   * will occur if any of the following conditions are true:
+   *  - the source of the drag initiated a link for dragging that
+   *    it itself cannot access. This prevents a source document from tricking
+   *    the user into a dragging a chrome url, for example.
+   *  - aDisallowInherit is true, and the URI being dropped would inherit the
+   *    current document's security context (URI_INHERITS_SECURITY_CONTEXT).
    *
    * aName is filled in with the link title if it exists, or an empty string
    * otherwise.
    */
-  AString dropLink(in nsIDOMDragEvent aEvent, out AString aName);
+  AString dropLink(in nsIDOMDragEvent aEvent, out AString aName,
+                   [optional] in boolean aDisallowInherit);
 };
--- a/content/base/src/contentAreaDropListener.js
+++ b/content/base/src/contentAreaDropListener.js
@@ -45,17 +45,17 @@ ContentAreaDropListener.prototype =
       let fileHandler = ioService.getProtocolHandler("file")
                                  .QueryInterface(Ci.nsIFileProtocolHandler);
       return [fileHandler.getURLSpecFromFile(file), file.leafName];
     }
 
     return [ ];
   },
 
-  _validateURI: function(dataTransfer, uriString)
+  _validateURI: function(dataTransfer, uriString, disallowInherit)
   {
     if (!uriString)
       return "";
 
     // Strip leading and trailing whitespace, then try to create a
     // URI from the dropped string. If that succeeds, we're
     // dropping a URI and we need to do a security check to make
     // sure the source document can load the dropped URI.
@@ -71,21 +71,25 @@ ContentAreaDropListener.prototype =
     } catch (ex) { }
     if (!uri)
       return uriString;
 
     // uriString is a valid URI, so do the security check.
     let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
                    getService(Ci.nsIScriptSecurityManager);
     let sourceNode = dataTransfer.mozSourceNode;
+    let flags = secMan.STANDARD;
+    if (disallowInherit)
+      flags |= secMan.DISALLOW_INHERIT_PRINCIPAL;
+
     // Use file:/// as the default uri so that drops of file URIs are always allowed
     if (sourceNode)
-      secMan.checkLoadURIStrWithPrincipal(sourceNode.nodePrincipal, uriString, secMan.STANDARD);
+      secMan.checkLoadURIStrWithPrincipal(sourceNode.nodePrincipal, uriString, flags);
     else
-      secMan.checkLoadURIStr("file:///", uriString, secMan.STANDARD);
+      secMan.checkLoadURIStr("file:///", uriString, flags);
 
     return uriString;
   },
 
   canDropLink: function(aEvent, aAllowSameDocument)
   {
     let dataTransfer = aEvent.dataTransfer;
     let types = dataTransfer.types;
@@ -115,25 +119,25 @@ ContentAreaDropListener.prototype =
       let sourceRoot = sourceDocument.defaultView.top;
       if (sourceRoot && sourceRoot == eventDocument.defaultView.top)
         return false;
     }
 
     return true;
   },
 
-  dropLink: function(aEvent, aName)
+  dropLink: function(aEvent, aName, aDisallowInherit)
   {
     aName.value = "";
 
     let dataTransfer = aEvent.dataTransfer;
     let [url, name] = this._getDropURL(dataTransfer);
 
     try {
-      url = this._validateURI(dataTransfer, url);
+      url = this._validateURI(dataTransfer, url, aDisallowInherit);
     } catch (ex) {
       aEvent.stopPropagation();
       aEvent.preventDefault();
       throw ex;
     }
 
     if (name)
       aName.value = name;
--- a/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp
+++ b/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp
@@ -945,17 +945,17 @@ nsDocShellTreeOwner::HandleEvent(nsIDOME
       handler->CanDropLink(dragEvent, false, &canDropLink);
       if (canDropLink)
         aEvent->PreventDefault();
     }
     else if (eventType.EqualsLiteral("drop")) {
       nsIWebNavigation* webnav = static_cast<nsIWebNavigation *>(mWebBrowser);
 
       nsAutoString link, name;
-      if (webnav && NS_SUCCEEDED(handler->DropLink(dragEvent, link, name))) {
+      if (webnav && NS_SUCCEEDED(handler->DropLink(dragEvent, link, false, name))) {
         if (!link.IsEmpty()) {
           webnav->LoadURI(link.get(), 0, nsnull, nsnull, nsnull);
         }
       }
       else {
         aEvent->StopPropagation();
         aEvent->PreventDefault();
       }
--- a/testing/mochitest/tests/SimpleTest/ChromeUtils.js
+++ b/testing/mochitest/tests/SimpleTest/ChromeUtils.js
@@ -241,18 +241,22 @@ function synthesizeDrop(srcElement, dest
   }
 
   ds.startDragSession();
 
   try {
     // need to use real mouse action
     aWindow.addEventListener("dragstart", trapDrag, true);
     synthesizeMouseAtCenter(srcElement, { type: "mousedown" }, aWindow);
-    synthesizeMouse(srcElement, 11, 11, { type: "mousemove" }, aWindow);
-    synthesizeMouse(srcElement, 20, 20, { type: "mousemove" }, aWindow);
+
+    var rect = srcElement.getBoundingClientRect();
+    var x = rect.width / 2;
+    var y = rect.height / 2;
+    synthesizeMouse(srcElement, x, y, { type: "mousemove" }, aWindow);
+    synthesizeMouse(srcElement, x+10, y+10, { type: "mousemove" }, aWindow);
     aWindow.removeEventListener("dragstart", trapDrag, true);
 
     event = aWindow.document.createEvent("DragEvents");
     event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
     gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true);
 
     var event = aWindow.document.createEvent("DragEvents");
     event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);