From ce492d0d77f99d3d89f6e682bc00f3209367e10f Mon Sep 17 00:00:00 2001 From: "edward.lee@engineering.uiuc.edu" Date: Sat, 23 Feb 2008 16:54:19 -0800 Subject: [PATCH] Bug 417798 - History can fill FF3 addressbar suggestions with unwanted bookmarklets that look almost like real pages but execute in the current page's scope. r=dietrich, b-ff3=beltzner --- browser/app/profile/firefox.js | 1 + .../components/places/src/nsNavHistory.cpp | 4 + toolkit/components/places/src/nsNavHistory.h | 1 + .../places/src/nsNavHistoryAutoComplete.cpp | 9 + .../places/tests/unit/test_417798.js | 232 ++++++++++++++++++ 5 files changed, 247 insertions(+) create mode 100644 toolkit/components/places/tests/unit/test_417798.js diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 19b6f2b2ef03..3bae053d0808 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -211,6 +211,7 @@ pref("browser.urlbar.doubleClickSelectsAll", false); #endif pref("browser.urlbar.autoFill", false); pref("browser.urlbar.matchOnlyTyped", false); +pref("browser.urlbar.filter.javascript", true); // the maximum number of results to show in autocomplete when doing richResults pref("browser.urlbar.maxRichResults", 25); diff --git a/toolkit/components/places/src/nsNavHistory.cpp b/toolkit/components/places/src/nsNavHistory.cpp index d82251d63f9f..2c1c4ee61d08 100644 --- a/toolkit/components/places/src/nsNavHistory.cpp +++ b/toolkit/components/places/src/nsNavHistory.cpp @@ -112,6 +112,7 @@ #define PREF_BROWSER_HISTORY_EXPIRE_DAYS_MAX "history_expire_days" #define PREF_BROWSER_HISTORY_EXPIRE_SITES "history_expire_sites" #define PREF_AUTOCOMPLETE_ONLY_TYPED "urlbar.matchOnlyTyped" +#define PREF_AUTOCOMPLETE_FILTER_JAVASCRIPT "urlbar.filter.javascript" #define PREF_AUTOCOMPLETE_ENABLED "urlbar.autocomplete.enabled" #define PREF_AUTOCOMPLETE_MAX_RICH_RESULTS "urlbar.maxRichResults" #define PREF_AUTOCOMPLETE_SEARCH_CHUNK_SIZE "urlbar.search.chunkSize" @@ -462,6 +463,7 @@ nsNavHistory::Init() nsCOMPtr pbi = do_QueryInterface(mPrefBranch); if (pbi) { pbi->AddObserver(PREF_AUTOCOMPLETE_ONLY_TYPED, this, PR_FALSE); + pbi->AddObserver(PREF_AUTOCOMPLETE_FILTER_JAVASCRIPT, this, PR_FALSE); pbi->AddObserver(PREF_AUTOCOMPLETE_MAX_RICH_RESULTS, this, PR_FALSE); pbi->AddObserver(PREF_AUTOCOMPLETE_SEARCH_CHUNK_SIZE, this, PR_FALSE); pbi->AddObserver(PREF_AUTOCOMPLETE_SEARCH_TIMEOUT, this, PR_FALSE); @@ -1716,6 +1718,8 @@ nsNavHistory::LoadPrefs(PRBool aInitializing) PRBool oldCompleteOnlyTyped = mAutoCompleteOnlyTyped; mPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ONLY_TYPED, &mAutoCompleteOnlyTyped); + mPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_FILTER_JAVASCRIPT, + &mAutoCompleteFilterJavascript); mPrefBranch->GetIntPref(PREF_AUTOCOMPLETE_MAX_RICH_RESULTS, &mAutoCompleteMaxResults); mPrefBranch->GetIntPref(PREF_AUTOCOMPLETE_SEARCH_CHUNK_SIZE, diff --git a/toolkit/components/places/src/nsNavHistory.h b/toolkit/components/places/src/nsNavHistory.h index fe562ce22834..2e97ebe0a008 100644 --- a/toolkit/components/places/src/nsNavHistory.h +++ b/toolkit/components/places/src/nsNavHistory.h @@ -667,6 +667,7 @@ protected: nsresult InitAutoComplete(); nsresult CreateAutoCompleteQueries(); PRBool mAutoCompleteOnlyTyped; + PRBool mAutoCompleteFilterJavascript; PRInt32 mAutoCompleteMaxResults; PRInt32 mAutoCompleteSearchChunkSize; PRInt32 mAutoCompleteSearchTimeout; diff --git a/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp b/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp index 743658e03fef..f8d2b7dbb2dc 100644 --- a/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp +++ b/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp @@ -428,6 +428,11 @@ nsNavHistory::AutoCompleteProcessSearch(mozIStorageStatement* aQuery, nsFaviconService* faviconService = nsFaviconService::GetFaviconService(); NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY); + // We want to filter javascript: URIs if the search doesn't start with it + const nsString &javascriptColon = NS_LITERAL_STRING("javascript:"); + PRBool filterJavascript = mAutoCompleteFilterJavascript && + mCurrentSearchString.Find(javascriptColon) != 0; + PRBool hasMore = PR_FALSE; // Determine the result of the search while (NS_SUCCEEDED(aQuery->ExecuteStep(&hasMore)) && hasMore) { @@ -435,6 +440,10 @@ nsNavHistory::AutoCompleteProcessSearch(mozIStorageStatement* aQuery, nsresult rv = aQuery->GetString(kAutoCompleteIndex_URL, escapedEntryURL); NS_ENSURE_SUCCESS(rv, rv); + // If we need to filter and have a javascript URI.. skip! + if (filterJavascript && escapedEntryURL.Find(javascriptColon) == 0) + continue; + // Prevent duplicates that might appear from previous searches such as tag // results and chunking. Because we use mCurrentResultURLs to remove // duplicates, the first url wins, so we might not show it as a "star" if diff --git a/toolkit/components/places/tests/unit/test_417798.js b/toolkit/components/places/tests/unit/test_417798.js new file mode 100644 index 000000000000..b3364eedab26 --- /dev/null +++ b/toolkit/components/places/tests/unit/test_417798.js @@ -0,0 +1,232 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** 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 Bug 378079 unit test code. + * + * The Initial Developer of the Original Code is POTI Inc. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Edward Lee + * + * 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 ***** */ + +/** + * Test for bug 417798 to make sure javascript: URIs don't show up unless the + * user searches for javascript: explicitly. + */ + +let current_test = 0; + +function AutoCompleteInput(aSearches) { + this.searches = aSearches; +} +AutoCompleteInput.prototype = { + constructor: AutoCompleteInput, + + searches: null, + + minResultsForPopup: 0, + timeout: 10, + searchParam: "", + textValue: "", + disableAutoComplete: false, + completeDefaultIndex: false, + + get searchCount() { + return this.searches.length; + }, + + getSearchAt: function(aIndex) { + return this.searches[aIndex]; + }, + + onSearchBegin: function() {}, + onSearchComplete: function() {}, + + popupOpen: false, + + popup: { + setSelectedIndex: function(aIndex) {}, + invalidate: function() {}, + + // nsISupports implementation + QueryInterface: function(iid) { + if (iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsIAutoCompletePopup)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } + }, + + // nsISupports implementation + QueryInterface: function(iid) { + if (iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsIAutoCompleteInput)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +} + +function ensure_results(aSearch, aExpected) +{ + let controller = Components.classes["@mozilla.org/autocomplete/controller;1"]. + getService(Components.interfaces.nsIAutoCompleteController); + + // Make an AutoCompleteInput that uses our searches + // and confirms results on search complete + let input = new AutoCompleteInput(["history"]); + + controller.input = input; + + var numSearchesStarted = 0; + input.onSearchBegin = function() { + numSearchesStarted++; + do_check_eq(numSearchesStarted, 1); + }; + + input.onSearchComplete = function() { + do_check_eq(numSearchesStarted, 1); + // If we expect results, make sure we got matches + do_check_eq(controller.searchStatus, aExpected.length ? + Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH : + Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH); + + // Make sure we have the right number of results + do_check_eq(controller.matchCount, aExpected.length); + + // Check to see the expected uris and titles match up (in any order) + for (let i = 0; i < controller.matchCount; i++) { + let value = controller.getValueAt(i); + let comment = controller.getCommentAt(i); + + print("Looking for an expected result of " + value + ", " + comment + "..."); + let j; + for (j = 0; j < aExpected.length; j++) { + let [uri, title] = aExpected[j]; + // Skip processed expected results + if (uri == undefined) continue; + // Load the real uri and titles + [uri, title] = [kURIs[uri], kTitles[title]]; + + // Got a match on both uri and title? + if (uri == value && title == comment) { + print("Got it at index " + j + "!!"); + // Make it undefined so we don't process it again + aExpected[j] = [,]; + break; + } + } + + // We didn't hit the break, so we must have not found it + if (j == aExpected.length) + do_throw("Didn't find the current result (" + value + ", " + comment + ") in expected: " + aExpected); + } + + // Fetch the next test if we have more + if (++current_test < gTests.length) + run_test(); + + do_test_finished(); + }; + + print("Searching for.. " + aSearch); + controller.startSearch(aSearch); +} + +// Get history services +try { + var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].getService(Ci.nsINavHistoryService); + var bhist = histsvc.QueryInterface(Ci.nsIBrowserHistory); + var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Ci.nsINavBookmarksService); + var iosvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); +} catch(ex) { + do_throw("Could not get services\n"); +} + +// Some date not too long ago +let gDate = new Date(Date.now() - 1000 * 60 * 60) * 1000; + +function addPageBook(aURI, aTitle, aBook) +{ + let uri = iosvc.newURI(kURIs[aURI], null, null); + let title = kTitles[aTitle]; + + print("Adding page/book: " + [aURI, aTitle, aBook, kURIs[aURI], title].join(", ")); + // Add the page and a visit for good measure + histsvc.setPageDetails(uri, title, 1, false, true); + histsvc.addVisit(uri, gDate, null, histsvc.TRANSITION_TYPED, false, 0); + + // Add a bookmark if we need to + if (aBook != undefined) { + let book = kTitles[aBook]; + bmsvc.insertBookmark(bmsvc.unfiledBookmarksFolder, uri, bmsvc.DEFAULT_INDEX, book); + } +} + +// Define some shared uris and titles +let kURIs = [ + "http://abc/def", + "javascript:5", +]; +let kTitles = [ + "Title with javascript:", +]; + +let kPages = [[0,0], [1,0]]; +for each (let [uri, title, book] in kPages) + addPageBook(uri, title, book); + +/** + * Test history autocomplete + */ +let gTests = [ + ["0: Match non-javascript: with plain search", + "a", [[0,0]]], + ["1: Match non-javascript: with almost javascript:", + "javascript", [[0,0]]], + ["2: Match javascript:", + "javascript:", [[0,0],[1,0]]], + ["3: Match nothing with non-first javascript:", + "5 javascript:", []], + ["4: Match javascript: with multi-word search", + "javascript: 5", [[1,0]]], +]; + +function run_test() { + print("\n"); + // Search is asynchronous, so don't let the test finish immediately + do_test_pending(); + + // Load the test and print a description then run the test + let [description, search, expected] = gTests[current_test]; + print(description); + ensure_results(search, expected); +}