Kill Google Safebrowsing.

This commit is contained in:
Pale Moon
2016-09-16 19:35:55 +02:00
committed by roytam1
parent df655aee01
commit 921c21a62b
120 changed files with 1 additions and 24630 deletions
-57
View File
@@ -338,63 +338,6 @@ pref("dom.w3c_touch_events.enabled", 1);
pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
#ifdef MOZ_SAFE_BROWSING
// Safe browsing does nothing unless this pref is set
pref("browser.safebrowsing.enabled", false);
// Prevent loading of pages identified as malware
pref("browser.safebrowsing.malware.enabled", false);
pref("browser.safebrowsing.debug", false);
pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.id", "Firefox");
// Tables for application reputation.
pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
// Non-enhanced mode (local url lists) URL list to check for updates
pref("browser.safebrowsing.provider.0.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client={moz:client}&appver={moz:version}&pver=2.2&key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.dataProvider", 0);
// Does the provider name need to be localizable?
pref("browser.safebrowsing.provider.0.name", "Google");
pref("browser.safebrowsing.provider.0.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
pref("browser.safebrowsing.provider.0.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client={moz:client}&appver={moz:version}&pver=2.2");
// HTML report pages
pref("browser.safebrowsing.provider.0.reportGenericURL", "http://{moz:locale}.phish-generic.mozilla.com/?hl={moz:locale}");
pref("browser.safebrowsing.provider.0.reportErrorURL", "http://{moz:locale}.phish-error.mozilla.com/?hl={moz:locale}");
pref("browser.safebrowsing.provider.0.reportPhishURL", "http://{moz:locale}.phish-report.mozilla.com/?hl={moz:locale}");
pref("browser.safebrowsing.provider.0.reportMalwareURL", "http://{moz:locale}.malware-report.mozilla.com/?hl={moz:locale}");
pref("browser.safebrowsing.provider.0.reportMalwareErrorURL", "http://{moz:locale}.malware-error.mozilla.com/?hl={moz:locale}");
// FAQ URLs
// The number of random entries to send with a gethash request.
pref("urlclassifier.gethashnoise", 4);
// Gethash timeout for Safebrowsing.
pref("urlclassifier.gethash.timeout_ms", 5000);
// If an urlclassifier table has not been updated in this number of seconds,
// a gethash request will be forced to check that the result is still in
// the database.
pref("urlclassifier.max-complete-age", 2700);
// URL for checking the reason for a malware warning.
pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
#endif
// True if this is the first time we are showing about:firstrun
pref("browser.firstrun.show.uidiscovery", true);
pref("browser.firstrun.show.localepicker", true);
-10
View File
@@ -70,11 +70,6 @@ XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
'nsICaptivePortalDetector');
#endif
#ifdef MOZ_SAFE_BROWSING
XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
"resource://gre/modules/SafeBrowsing.jsm");
#endif
function getContentWindow() {
return shell.contentBrowser.contentWindow;
}
@@ -353,11 +348,6 @@ var shell = {
ppmm.addMessageListener("sms-handler", this);
ppmm.addMessageListener("mail-handler", this);
ppmm.addMessageListener("file-picker", this);
#ifdef MOZ_SAFE_BROWSING
setTimeout(function() {
SafeBrowsing.init();
}, 5000);
#endif
},
stop: function shell_stop() {
-1
View File
@@ -17,7 +17,6 @@ MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
# MOZ_APP_DISPLAYNAME is set by branding/configure.sh
MOZ_SAFE_BROWSING=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_METRICS=1
MOZ_CAPTIVEDETECT=1
-44
View File
@@ -726,46 +726,6 @@ pref("goanna.handlerService.schemes.ircs.3.uriTemplate", "chrome://browser-regio
// By default, we don't want protocol/content handlers to be registered from a different host, see bug 402287
pref("goanna.handlerService.allowRegisterFromDifferentHost", false);
#ifdef MOZ_SAFE_BROWSING
pref("browser.safebrowsing.enabled", true);
pref("browser.safebrowsing.malware.enabled", true);
pref("browser.safebrowsing.debug", false);
pref("browser.safebrowsing.updateURL", "http://safebrowsing.clients.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.keyURL", "https://sb-ssl.google.com/safebrowsing/newkey?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.gethashURL", "http://safebrowsing.clients.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/report?");
pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/phishing-protection/");
pref("browser.safebrowsing.malware.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
#ifdef MOZILLA_OFFICIAL
// Normally the "client ID" sent in updates is appinfo.name, but for
// official Firefox releases from Mozilla we use a special identifier.
pref("browser.safebrowsing.id", "navclient-auto-ffox");
#endif
// Name of the about: page contributed by safebrowsing to handle display of error
// pages on phishing/malware hits. (bug 399233)
pref("urlclassifier.alternate_error_page", "blocked");
// The number of random entries to send with a gethash request.
pref("urlclassifier.gethashnoise", 4);
// The list of tables that use the gethash request to confirm partial results.
pref("urlclassifier.gethashtables", "goog-phish-shavar,goog-malware-shavar");
// If an urlclassifier table has not been updated in this number of seconds,
// a gethash request will be forced to check that the result is still in
// the database.
pref("urlclassifier.max-complete-age", 2700);
#endif
pref("browser.geolocation.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/geolocation/");
pref("browser.mixedcontent.warning.infoURL", "http://support.mozilla.org/1/%APP%/%VERSION%/%OS%/%LOCALE%/mixed-content/");
@@ -950,10 +910,6 @@ pref("services.sync.prefs.sync.browser.download.manager.showWhenStarting", true)
pref("services.sync.prefs.sync.browser.formfill.enable", true);
pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
pref("services.sync.prefs.sync.browser.offline-apps.notify", true);
#ifdef MOZ_SAFE_BROWSING
pref("services.sync.prefs.sync.browser.safebrowsing.enabled", true);
pref("services.sync.prefs.sync.browser.safebrowsing.malware.enabled", true);
#endif
pref("services.sync.prefs.sync.browser.search.selectedEngine", true);
pref("services.sync.prefs.sync.browser.search.update", true);
pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true);
-4
View File
@@ -13,10 +13,6 @@
%customizeToolbarDTD;
<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
%placesDTD;
#ifdef MOZ_SAFE_BROWSING
<!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
%safebrowsingDTD;
#endif
<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
%aboutHomeDTD;
]>
@@ -1,53 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifdef MOZ_SAFE_BROWSING
var gSafeBrowsing = {
setReportPhishingMenu: function() {
// A phishing page will have a specific about:blocked content documentURI
var isPhishingPage = content.document.documentURI.startsWith("about:blocked?e=phishingBlocked");
// Show/hide the appropriate menu item.
document.getElementById("menu_HelpPopup_reportPhishingtoolmenu")
.hidden = isPhishingPage;
document.getElementById("menu_HelpPopup_reportPhishingErrortoolmenu")
.hidden = !isPhishingPage;
var broadcasterId = isPhishingPage
? "reportPhishingErrorBroadcaster"
: "reportPhishingBroadcaster";
var broadcaster = document.getElementById(broadcasterId);
if (!broadcaster)
return;
var uri = getBrowser().currentURI;
if (uri && (uri.schemeIs("http") || uri.schemeIs("https")))
broadcaster.removeAttribute("disabled");
else
broadcaster.setAttribute("disabled", true);
},
/**
* Used to report a phishing page or a false positive
* @param name String One of "Phish", "Error", "Malware" or "MalwareError"
* @return String the report phishing URL.
*/
getReportURL: function(name) {
var reportUrl = SafeBrowsing.getReportURL(name);
var pageUri = gBrowser.currentURI.clone();
// Remove the query to avoid including potentially sensitive data
if (pageUri instanceof Ci.nsIURL)
pageUri.query = '';
reportUrl += "&url=" + encodeURIComponent(pageUri.asciiSpec);
return reportUrl;
}
}
#endif
-127
View File
@@ -125,11 +125,6 @@ XPCOMUtils.defineLazyGetter(this, "BrowserDebuggerProcess", function() {
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
"resource://gre/modules/PageThumbs.jsm");
#ifdef MOZ_SAFE_BROWSING
XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
"resource://gre/modules/SafeBrowsing.jsm");
#endif
XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
"resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
@@ -154,9 +149,6 @@ let gInitialPages = [
#include browser-fullZoom.js
#include browser-places.js
#include browser-plugins.js
#ifdef MOZ_SAFE_BROWSING
#include browser-safebrowsing.js
#endif
#include browser-tabPreviews.js
#include browser-thumbnails.js
#include browser-webrtcUI.js
@@ -984,11 +976,6 @@ var gBrowserInit = {
loadOneOrMoreURIs(uriToLoad);
}
#ifdef MOZ_SAFE_BROWSING
// Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
setTimeout(function() { SafeBrowsing.init(); }, 2000);
#endif
Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
@@ -2374,11 +2361,6 @@ let BrowserOnClick = {
if (ownerDoc.documentURI.startsWith("about:certerror")) {
this.onAboutCertError(originalTarget, ownerDoc);
}
#ifdef MOZ_SAFE_BROWSING
else if (ownerDoc.documentURI.startsWith("about:blocked")) {
this.onAboutBlocked(originalTarget, ownerDoc);
}
#endif
else if (ownerDoc.documentURI.startsWith("about:neterror")) {
this.onAboutNetError(originalTarget, ownerDoc);
}
@@ -2428,115 +2410,6 @@ let BrowserOnClick = {
}
},
#ifdef MOZ_SAFE_BROWSING
onAboutBlocked: function BrowserOnClick_onAboutBlocked(aTargetElm, aOwnerDoc) {
let elmId = aTargetElm.getAttribute("id");
// The event came from a button on a malware/phishing block page
// First check whether it's malware or phishing, so that we can
// use the right strings/links
let isMalware = /e=malwareBlocked/.test(aOwnerDoc.documentURI);
let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_";
let nsISecTel = Ci.nsISecurityUITelemetry;
let isIframe = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
bucketName += isIframe ? "TOP_" : "FRAME_";
switch (elmId) {
case "getMeOutButton":
getMeOutOfHere();
break;
case "reportButton":
// This is the "Why is this site blocked" button. For malware,
// we can fetch a site-specific report, for phishing, we redirect
// to the generic page describing phishing protection.
if (isMalware) {
// Get the stop badware "why is this blocked" report url,
// append the current url, and go there.
try {
let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
reportURL += aOwnerDoc.location.href;
content.location = reportURL;
} catch (e) {
Components.utils.reportError("Couldn't get malware report URL: " + e);
}
}
else { // It's a phishing site, not malware
try {
content.location = formatURL("browser.safebrowsing.warning.infoURL", true);
} catch (e) {
Components.utils.reportError("Couldn't get phishing info URL: " + e);
}
}
break;
case "ignoreWarningButton":
this.ignoreWarningButton(isMalware);
break;
}
},
ignoreWarningButton: function BrowserOnClick_ignoreWarningButton(aIsMalware) {
// Allow users to override and continue through to the site,
// but add a notify bar as a reminder, so that they don't lose
// track after, e.g., tab switching.
gBrowser.loadURIWithFlags(content.location.href,
nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
null, null, null);
Services.perms.add(makeURI(content.location.href), "safe-browsing",
Ci.nsIPermissionManager.ALLOW_ACTION,
Ci.nsIPermissionManager.EXPIRE_SESSION);
let buttons = [{
label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
callback: function() { getMeOutOfHere(); }
}];
let title;
if (aIsMalware) {
title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
buttons[1] = {
label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
callback: function() {
openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab');
}
};
} else {
title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery");
buttons[1] = {
label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"),
callback: function() {
openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');
}
};
}
let notificationBox = gBrowser.getNotificationBox();
let value = "blocked-badware-page";
let previousNotification = notificationBox.getNotificationWithValue(value);
if (previousNotification) {
notificationBox.removeNotification(previousNotification);
}
let notification = notificationBox.appendNotification(
title,
value,
"chrome://global/skin/icons/blacklist_favicon.png",
notificationBox.PRIORITY_CRITICAL_HIGH,
buttons
);
// Persist the notification until the user removes so it
// doesn't get removed on redirects.
notification.persistence = -1;
},
#endif
onAboutNetError: function BrowserOnClick_onAboutNetError(aTargetElm, aOwnerDoc) {
let elmId = aTargetElm.getAttribute("id");
if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI))
-5
View File
@@ -131,8 +131,3 @@ browser.jar:
# the following files are browser-specific overrides
* content/browser/license.html (/toolkit/content/license.html)
% override chrome://global/content/license.html chrome://browser/content/license.html
#ifdef MOZ_SAFE_BROWSING
content/browser/report-phishing-overlay.xul (content/report-phishing-overlay.xul)
content/browser/blockedSite.xhtml (content/blockedSite.xhtml)
% overlay chrome://browser/content/browser.xul chrome://browser/content/report-phishing-overlay.xul
#endif
@@ -33,12 +33,6 @@ struct RedirEntry {
URI_SAFE_FOR_UNTRUSTED_CONTENT.
*/
static RedirEntry kRedirMap[] = {
#ifdef MOZ_SAFE_BROWSING
{ "blocked", "chrome://browser/content/blockedSite.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
#endif
{ "certerror", "chrome://browser/content/certerror/aboutCertError.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
-3
View File
@@ -85,9 +85,6 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
{ NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
#endif
{ NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID },
#ifdef MOZ_SAFE_BROWSING
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "blocked", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
-3
View File
@@ -23,9 +23,6 @@ DIRS += [
if CONFIG['MOZ_BROWSER_SIDEBAR']:
DIRS += ['sidebar']
if CONFIG['MOZ_SAFE_BROWSING']:
DIRS += ['safebrowsing']
DIRS += ['build']
XPIDL_SOURCES += [
@@ -66,18 +66,6 @@
accesskey="&addonExceptions.accesskey;"
oncommand="gSecurityPane.showAddonExceptions();"/>
</hbox>
#ifdef MOZ_SAFE_BROWSING
<separator class="thin"/>
<checkbox id="blockAttackSites"
label="&blockAttackSites.label;"
accesskey="&blockAttackSites.accesskey;"
preference="browser.safebrowsing.malware.enabled" />
<checkbox id="blockWebForgeries"
label="&blockWebForgeries.label;"
accesskey="&blockWebForgeries.accesskey;"
preference="browser.safebrowsing.enabled" />
#endif
</groupbox>
<!-- Passwords -->
@@ -1,7 +0,0 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-9
View File
@@ -635,15 +635,6 @@
@RESPATH@/browser/modules/*
@RESPATH@/modules/*
; Safe Browsing
#ifdef MOZ_URL_CLASSIFIER
@RESPATH@/components/nsURLClassifier.manifest
@RESPATH@/components/nsUrlClassifierHashCompleter.js
@RESPATH@/components/nsUrlClassifierListManager.js
@RESPATH@/components/nsUrlClassifierLib.js
@RESPATH@/components/url-classifier.xpt
#endif
; ANGLE GLES-on-D3D rendering library
#ifdef MOZ_ANGLE_RENDERER
@BINPATH@/libEGL.dll
@@ -1,18 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY safeb.palm.accept.label "Get me out of here!">
<!ENTITY safeb.palm.decline.label "Ignore this warning">
<!ENTITY safeb.palm.notforgery.label2 "This isn't a web forgery…">
<!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
<!ENTITY safeb.blocked.malwarePage.title "Reported Attack Page!">
<!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
<!ENTITY safeb.blocked.malwarePage.shortDesc "This web page at <span id='malware_sitename'/> has been reported as an attack page and has been blocked based on your security preferences.">
<!ENTITY safeb.blocked.malwarePage.longDesc "<p>Attack pages try to install programs that steal private information, use your computer to attack others, or damage your system.</p><p>Some attack pages intentionally distribute harmful software, but many are compromised without the knowledge or permission of their owners.</p>">
<!ENTITY safeb.blocked.phishingPage.title "Reported Web Forgery!">
<!-- Localization note (safeb.blocked.phishing.shortDesc) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
<!ENTITY safeb.blocked.phishingPage.shortDesc "This web page at <span id='phishing_sitename'/> has been reported as a web forgery and has been blocked based on your security preferences.">
<!ENTITY safeb.blocked.phishingPage.longDesc "<p>Web forgeries are designed to trick you into revealing personal or financial information by imitating sources you may trust.</p><p>Entering any information on this web page may result in identity theft or other fraud.</p>">
@@ -1,6 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY reportPhishSiteMenu.title2 "Report Web Forgery…">
<!ENTITY reportPhishSiteMenu.accesskey "F">
-4
View File
@@ -101,10 +101,6 @@
locale/browser/places/bookmarkProperties.properties (%chrome/browser/places/bookmarkProperties.properties)
locale/browser/preferences/selectBookmark.dtd (%chrome/browser/preferences/selectBookmark.dtd)
locale/browser/places/moveBookmarks.dtd (%chrome/browser/places/moveBookmarks.dtd)
#ifdef MOZ_SAFE_BROWSING
locale/browser/safebrowsing/phishing-afterload-warning-message.dtd (%chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd)
locale/browser/safebrowsing/report-phishing.dtd (%chrome/browser/safebrowsing/report-phishing.dtd)
#endif
locale/browser/feeds/subscribe.dtd (%chrome/browser/feeds/subscribe.dtd)
locale/browser/feeds/subscribe.properties (%chrome/browser/feeds/subscribe.properties)
locale/browser/migration/migration.dtd (%chrome/browser/migration/migration.dtd)
-30
View File
@@ -3895,14 +3895,12 @@ MOZ_SOCIAL=1
MOZ_PREF_EXTENSIONS=1
MOZ_PROFILELOCKING=1
MOZ_REFLOW_PERF=
MOZ_SAFE_BROWSING=
MOZ_HELP_VIEWER=
MOZ_SPELLCHECK=1
MOZ_ANDROID_APZ=
MOZ_TOOLKIT_SEARCH=1
MOZ_UI_LOCALE=en-US
MOZ_UNIVERSALCHARDET=1
MOZ_URL_CLASSIFIER=
MOZ_XUL=1
MOZ_ZIPWRITER=1
NS_PRINTING=1
@@ -6675,34 +6673,6 @@ if test -n "$MOZ_HELP_VIEWER"; then
AC_DEFINE(MOZ_HELP_VIEWER)
fi
dnl ========================================================
dnl = Enable safe browsing (anti-phishing)
dnl ========================================================
MOZ_ARG_ENABLE_BOOL(safe-browsing,
[ --enable-safe-browsing Enable safe browsing (anti-phishing) implementation],
MOZ_SAFE_BROWSING=1,
MOZ_SAFE_BROWSING= )
if test -n "$MOZ_SAFE_BROWSING"; then
AC_DEFINE(MOZ_SAFE_BROWSING)
fi
AC_SUBST(MOZ_SAFE_BROWSING)
dnl ========================================================
dnl = Enable url-classifier
dnl ========================================================
dnl Implicitly enabled by default if building with safe-browsing
if test -n "$MOZ_SAFE_BROWSING"; then
MOZ_URL_CLASSIFIER=1
fi
MOZ_ARG_ENABLE_BOOL(url-classifier,
[ --enable-url-classifier Enable url classifier module],
MOZ_URL_CLASSIFIER=1,
MOZ_URL_CLASSIFIER= )
if test -n "$MOZ_URL_CLASSIFIER"; then
AC_DEFINE(MOZ_URL_CLASSIFIER)
fi
AC_SUBST(MOZ_URL_CLASSIFIER)
dnl ========================================================
dnl = Disable zipwriter
dnl ========================================================
-34
View File
@@ -576,40 +576,6 @@ pref("shumway.disabled", true);
// enable touch events interfaces
pref("dom.w3c_touch_events.enabled", 1);
#ifdef MOZ_SAFE_BROWSING
pref("browser.safebrowsing.enabled", true);
pref("browser.safebrowsing.malware.enabled", true);
pref("browser.safebrowsing.debug", false);
pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
pref("browser.safebrowsing.id", @MOZ_APP_UA_NAME@);
// Name of the about: page contributed by safebrowsing to handle display of error
// pages on phishing/malware hits. (bug 399233)
pref("urlclassifier.alternate_error_page", "blocked");
// The number of random entries to send with a gethash request.
pref("urlclassifier.gethashnoise", 4);
// Gethash timeout for Safebrowsing.
pref("urlclassifier.gethash.timeout_ms", 5000);
// If an urlclassifier table has not been updated in this number of seconds,
// a gethash request will be forced to check that the result is still in
// the database.
pref("urlclassifier.max-complete-age", 2700);
#endif
// URL for posting tiles metrics.
#ifdef RELEASE_BUILD
pref("browser.tiles.reportURL", "https://tiles.services.mozilla.com/v2/links/click");
-10
View File
@@ -57,11 +57,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
if (AppConstants.MOZ_SAFE_BROWSING) {
XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
"resource://gre/modules/SafeBrowsing.jsm");
}
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
@@ -381,11 +376,6 @@ var BrowserApp = {
CastingApps.init();
DownloadNotifications.init();
if (AppConstants.MOZ_SAFE_BROWSING) {
// Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
SafeBrowsing.init();
};
// Delay this a minute because there's no rush
setTimeout(() => {
BrowserApp.gmpInstallManager = new GMPInstallManager();
@@ -15,9 +15,6 @@ contract @mozilla.org/network/protocol/about;1?what=privatebrowsing {322ba47e-70
#ifdef MOZ_SERVICES_HEALTHREPORT
contract @mozilla.org/network/protocol/about;1?what=healthreport {322ba47e-7047-4f71-aebf-cb7d69325cd9}
#endif
#ifdef MOZ_SAFE_BROWSING
contract @mozilla.org/network/protocol/about;1?what=blocked {322ba47e-7047-4f71-aebf-cb7d69325cd9}
#endif
#ifdef MOZ_DEVICES
contract @mozilla.org/network/protocol/about;1?what=devices {322ba47e-7047-4f71-aebf-cb7d69325cd9}
#endif
-2
View File
@@ -16,8 +16,6 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=mobile/android/branding/official
# See the --enable-android-min-sdk and --enable-android-max-sdk arguments in configure.in.
MOZ_ANDROID_MIN_SDK_VERSION=9
MOZ_SAFE_BROWSING=1
MOZ_NO_SMART_CARDS=1
# Enable getUserMedia
@@ -457,15 +457,6 @@
; Modules
@BINPATH@/modules/*
#ifdef MOZ_SAFE_BROWSING
; Safe Browsing
@BINPATH@/components/nsURLClassifier.manifest
@BINPATH@/components/nsUrlClassifierHashCompleter.js
@BINPATH@/components/nsUrlClassifierListManager.js
@BINPATH@/components/nsUrlClassifierLib.js
@BINPATH@/components/url-classifier.xpt
#endif
; GNOME hooks
#ifdef MOZ_ENABLE_GNOME_COMPONENT
@BINPATH@/components/@DLL_PREFIX@mozgnome@DLL_SUFFIX@
@@ -606,9 +597,6 @@ bin/libfreebl_32int64_3.so
@BINPATH@/components/PaymentsUI.js
@BINPATH@/components/PaymentProviderStrategy.js
#ifdef MOZ_SAFE_BROWSING
@BINPATH@/components/SafeBrowsing.jsm
#endif
@BINPATH@/components/XPIDialogService.js
@BINPATH@/components/browsercomps.xpt
-7
View File
@@ -63,13 +63,6 @@ let AppConstants = Object.freeze({
false,
#endif
MOZ_SAFE_BROWSING:
#ifdef MOZ_SAFE_BROWSING
true,
#else
false,
#endif
MOZ_TELEMETRY_REPORTING:
#ifdef MOZ_TELEMETRY_REPORTING
true,
+1 -2
View File
@@ -36,8 +36,7 @@ for var in ('ANDROID_PACKAGE_NAME', 'MOZ_APP_VERSION'):
for var in ('NIGHTLY_BUILD', 'RELEASE_BUILD', 'ACCESSIBILITY',
'MOZILLA_OFFICIAL', 'MOZ_OFFICIAL_BRANDING', 'MOZ_SERVICES_HEALTHREPORT',
'MOZ_DEVICES', 'MOZ_DEVICES', 'MOZ_SAFE_BROWSING',
'MOZ_TELEMETRY_REPORTING', 'MOZ_WEBRTC'):
'MOZ_DEVICES', 'MOZ_DEVICES', 'MOZ_TELEMETRY_REPORTING', 'MOZ_WEBRTC'):
if CONFIG[var]:
DEFINES[var] = 1
-1
View File
@@ -25,7 +25,6 @@ LOCAL_INCLUDES += [
'../startup',
'../statusfilter',
'../typeaheadfind',
'../url-classifier',
]
if not CONFIG['MOZ_DISABLE_PARENTAL_CONTROLS']:
@@ -24,14 +24,6 @@
#include "nsTypeAheadFind.h"
#ifdef MOZ_URL_CLASSIFIER
#include "ApplicationReputation.h"
#include "nsUrlClassifierDBService.h"
#include "nsUrlClassifierStreamUpdater.h"
#include "nsUrlClassifierUtils.h"
#include "nsUrlClassifierPrefixSet.h"
#endif
#include "nsBrowserStatusFilter.h"
#include "mozilla/FinalizationWitnessService.h"
#include "mozilla/NativeOSFileInternals.h"
@@ -77,33 +69,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadProxy)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsTypeAheadFind)
#ifdef MOZ_URL_CLASSIFIER
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ApplicationReputationService,
ApplicationReputationService::GetSingleton)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsUrlClassifierPrefixSet)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsUrlClassifierStreamUpdater)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUrlClassifierUtils, Init)
static nsresult
nsUrlClassifierDBServiceConstructor(nsISupports *aOuter, REFNSIID aIID,
void **aResult)
{
nsresult rv;
NS_ENSURE_ARG_POINTER(aResult);
NS_ENSURE_NO_AGGREGATION(aOuter);
nsUrlClassifierDBService *inst = nsUrlClassifierDBService::GetInstance(&rv);
if (nullptr == inst) {
return rv;
}
/* NS_ADDREF(inst); */
rv = inst->QueryInterface(aIID, aResult);
NS_RELEASE(inst);
return rv;
}
#endif
NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
#if defined(USE_MOZ_UPDATER)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
@@ -128,13 +93,6 @@ NS_DEFINE_NAMED_CID(NS_DOWNLOADPLATFORM_CID);
NS_DEFINE_NAMED_CID(NS_DOWNLOAD_CID);
NS_DEFINE_NAMED_CID(NS_FIND_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_TYPEAHEADFIND_CID);
#ifdef MOZ_URL_CLASSIFIER
NS_DEFINE_NAMED_CID(NS_APPLICATION_REPUTATION_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERPREFIXSET_CID);
NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERDBSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTREAMUPDATER_CID);
NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID);
#endif
NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
#if defined(USE_MOZ_UPDATER)
NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
@@ -159,13 +117,6 @@ static const Module::CIDEntry kToolkitCIDs[] = {
{ &kNS_DOWNLOAD_CID, false, nullptr, nsDownloadProxyConstructor },
{ &kNS_FIND_SERVICE_CID, false, nullptr, nsFindServiceConstructor },
{ &kNS_TYPEAHEADFIND_CID, false, nullptr, nsTypeAheadFindConstructor },
#ifdef MOZ_URL_CLASSIFIER
{ &kNS_APPLICATION_REPUTATION_SERVICE_CID, false, nullptr, ApplicationReputationServiceConstructor },
{ &kNS_URLCLASSIFIERPREFIXSET_CID, false, nullptr, nsUrlClassifierPrefixSetConstructor },
{ &kNS_URLCLASSIFIERDBSERVICE_CID, false, nullptr, nsUrlClassifierDBServiceConstructor },
{ &kNS_URLCLASSIFIERSTREAMUPDATER_CID, false, nullptr, nsUrlClassifierStreamUpdaterConstructor },
{ &kNS_URLCLASSIFIERUTILS_CID, false, nullptr, nsUrlClassifierUtilsConstructor },
#endif
{ &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor },
#if defined(USE_MOZ_UPDATER)
{ &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
@@ -191,14 +142,6 @@ static const Module::ContractIDEntry kToolkitContracts[] = {
{ NS_DOWNLOADPLATFORM_CONTRACTID, &kNS_DOWNLOADPLATFORM_CID },
{ NS_FIND_SERVICE_CONTRACTID, &kNS_FIND_SERVICE_CID },
{ NS_TYPEAHEADFIND_CONTRACTID, &kNS_TYPEAHEADFIND_CID },
#ifdef MOZ_URL_CLASSIFIER
{ NS_APPLICATION_REPUTATION_SERVICE_CONTRACTID, &kNS_APPLICATION_REPUTATION_SERVICE_CID },
{ NS_URLCLASSIFIERPREFIXSET_CONTRACTID, &kNS_URLCLASSIFIERPREFIXSET_CID },
{ NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID },
{ NS_URICLASSIFIERSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID },
{ NS_URLCLASSIFIERSTREAMUPDATER_CONTRACTID, &kNS_URLCLASSIFIERSTREAMUPDATER_CID },
{ NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID },
#endif
{ NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
#if defined(USE_MOZ_UPDATER)
{ NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
File diff suppressed because it is too large Load Diff
@@ -1,55 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ApplicationReputation_h__
#define ApplicationReputation_h__
#include "nsIApplicationReputation.h"
#include "nsIRequestObserver.h"
#include "nsIStreamListener.h"
#include "nsISupports.h"
#include "nsCOMPtr.h"
#include "nsString.h"
class nsIRequest;
class PendingDBLookup;
class PendingLookup;
struct PRLogModuleInfo;
class ApplicationReputationService final :
public nsIApplicationReputationService {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIAPPLICATIONREPUTATIONSERVICE
public:
static ApplicationReputationService* GetSingleton();
private:
friend class PendingLookup;
friend class PendingDBLookup;
/**
* Global singleton object for holding this factory service.
*/
static ApplicationReputationService* gApplicationReputationService;
/**
* NSPR_LOG_MODULES=ApplicationReputation:5
*/
static PRLogModuleInfo* prlog;
/**
* This is a singleton, so disallow construction.
*/
ApplicationReputationService();
~ApplicationReputationService();
/**
* Wrapper function for QueryReputation that makes it easier to ensure the
* callback is called.
*/
nsresult QueryReputationInternal(nsIApplicationReputationQuery* aQuery,
nsIApplicationReputationCallback* aCallback);
};
#endif /* ApplicationReputation_h__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,25 +0,0 @@
#!/bin/bash
# A script to generate toolkit/components/downloads/csd.pb.{cc,h} for use in
# nsIApplicationReputationQuery. This script assumes you have downloaded and
# installed the protocol buffer compiler.
# As of June 26 2014, csd.proto contains many protobufs that are currently
# unused by ApplicationReputation. You may want to strip csd.proto of these
# before running the protocol compiler on it.
if [ -n $PROTOC_PATH ]; then
PROTOC_PATH=/usr/local/bin/protoc
fi
echo "Using $PROTOC_PATH as protocol compiler"
if [ ! -e $PROTOC_PATH ]; then
echo "You must install the protocol compiler from " \
"https://code.google.com/p/protobuf/downloads/list"
exit 1
fi
# Get the protocol buffer and compile it
CMD='wget http://src.chromium.org/chrome/trunk/src/chrome/common/safe_browsing/csd.proto -O csd.proto'
OUTPUT_PATH=toolkit/components/downloads
$CMD
$PROTOC_PATH csd.proto --cpp_out=$OUTPUT_PATH
-6
View File
@@ -30,12 +30,6 @@ SOURCES += [
'SQLFunctions.cpp',
]
if CONFIG['MOZ_URL_CLASSIFIER']:
UNIFIED_SOURCES += [
'ApplicationReputation.cpp',
'csd.pb.cc'
]
if CONFIG['OS_ARCH'] == 'WINNT':
# Can't build unified because we need CreateEvent which some IPC code
# included in LoadContext ends up undefining.
@@ -136,11 +136,7 @@ this.DownloadIntegration = {
dontLoadObservers: false,
dontCheckParentalControls: false,
shouldBlockInTest: false,
#ifdef MOZ_URL_CLASSIFIER
dontCheckApplicationReputation: false,
#else
dontCheckApplicationReputation: true,
#endif
shouldBlockInTestForApplicationReputation: false,
shouldKeepBlockedDataInTest: false,
dontOpenFileAndFolder: false,
-3
View File
@@ -78,9 +78,6 @@ if CONFIG['MOZ_XUL']:
if CONFIG['MOZ_TOOLKIT_SEARCH']:
DIRS += ['search']
if CONFIG['MOZ_URL_CLASSIFIER']:
DIRS += ['url-classifier']
if CONFIG['MOZ_CAPTIVEDETECT']:
DIRS += ['captivedetect']
@@ -1,102 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ChunkSet.h"
namespace mozilla {
namespace safebrowsing {
nsresult
ChunkSet::Serialize(nsACString& aChunkStr)
{
aChunkStr.Truncate();
uint32_t i = 0;
while (i < mChunks.Length()) {
if (i != 0) {
aChunkStr.Append(',');
}
aChunkStr.AppendInt((int32_t)mChunks[i]);
uint32_t first = i;
uint32_t last = first;
i++;
while (i < mChunks.Length() && (mChunks[i] == mChunks[i - 1] + 1 || mChunks[i] == mChunks[i - 1])) {
last = i++;
}
if (last != first) {
aChunkStr.Append('-');
aChunkStr.AppendInt((int32_t)mChunks[last]);
}
}
return NS_OK;
}
nsresult
ChunkSet::Set(uint32_t aChunk)
{
size_t idx = mChunks.BinaryIndexOf(aChunk);
if (idx == nsTArray<uint32_t>::NoIndex) {
mChunks.InsertElementSorted(aChunk);
}
return NS_OK;
}
nsresult
ChunkSet::Unset(uint32_t aChunk)
{
mChunks.RemoveElementSorted(aChunk);
return NS_OK;
}
bool
ChunkSet::Has(uint32_t aChunk) const
{
return mChunks.BinaryIndexOf(aChunk) != nsTArray<uint32_t>::NoIndex;
}
nsresult
ChunkSet::Merge(const ChunkSet& aOther)
{
const uint32_t *dupIter = aOther.mChunks.Elements();
const uint32_t *end = aOther.mChunks.Elements() + aOther.mChunks.Length();
for (const uint32_t *iter = dupIter; iter != end; iter++) {
nsresult rv = Set(*iter);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
ChunkSet::Remove(const ChunkSet& aOther)
{
uint32_t *addIter = mChunks.Elements();
uint32_t *end = mChunks.Elements() + mChunks.Length();
for (uint32_t *iter = addIter; iter != end; iter++) {
if (!aOther.Has(*iter)) {
*addIter = *iter;
addIter++;
}
}
mChunks.SetLength(addIter - mChunks.Elements());
return NS_OK;
}
void
ChunkSet::Clear()
{
mChunks.Clear();
}
}
}
@@ -1,57 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ChunkSet_h__
#define ChunkSet_h__
#include "Entries.h"
#include "nsString.h"
#include "nsTArray.h"
namespace mozilla {
namespace safebrowsing {
/**
* Store the chunk numbers as an array of uint32_t. We need chunk numbers in
* order to ask for incremental updates from the server.
* XXX: We should optimize this further to compress the many consecutive
* numbers.
*/
class ChunkSet {
public:
ChunkSet() {}
~ChunkSet() {}
nsresult Serialize(nsACString& aStr);
nsresult Set(uint32_t aChunk);
nsresult Unset(uint32_t aChunk);
void Clear();
nsresult Merge(const ChunkSet& aOther);
nsresult Remove(const ChunkSet& aOther);
bool Has(uint32_t chunk) const;
uint32_t Length() const { return mChunks.Length(); }
nsresult Write(nsIOutputStream* aOut) {
return WriteTArray(aOut, mChunks);
}
nsresult Read(nsIInputStream* aIn, uint32_t aNumElements) {
return ReadTArray(aIn, &mChunks, aNumElements);
}
uint32_t *Begin() { return mChunks.Elements(); }
uint32_t *End() { return mChunks.Elements() + mChunks.Length(); }
private:
nsTArray<uint32_t> mChunks;
};
}
}
#endif
@@ -1,755 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Classifier.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsISimpleEnumerator.h"
#include "nsIRandomGenerator.h"
#include "nsIInputStream.h"
#include "nsISeekableStream.h"
#include "nsIFile.h"
#include "nsThreadUtils.h"
#include "mozilla/Telemetry.h"
#include "prlog.h"
// NSPR_LOG_MODULES=UrlClassifierDbService:5
extern PRLogModuleInfo *gUrlClassifierDbServiceLog;
#if defined(PR_LOGGING)
#define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args)
#define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierDbServiceLog, 4)
#else
#define LOG(args)
#define LOG_ENABLED() (false)
#endif
#define STORE_DIRECTORY NS_LITERAL_CSTRING("safebrowsing")
#define TO_DELETE_DIR_SUFFIX NS_LITERAL_CSTRING("-to_delete")
#define BACKUP_DIR_SUFFIX NS_LITERAL_CSTRING("-backup")
namespace mozilla {
namespace safebrowsing {
void
Classifier::SplitTables(const nsACString& str, nsTArray<nsCString>& tables)
{
tables.Clear();
nsACString::const_iterator begin, iter, end;
str.BeginReading(begin);
str.EndReading(end);
while (begin != end) {
iter = begin;
FindCharInReadable(',', iter, end);
nsDependentCSubstring table = Substring(begin,iter);
if (!table.IsEmpty()) {
tables.AppendElement(Substring(begin, iter));
}
begin = iter;
if (begin != end) {
begin++;
}
}
}
Classifier::Classifier()
{
}
Classifier::~Classifier()
{
Close();
}
nsresult
Classifier::SetupPathNames()
{
// Get the root directory where to store all the databases.
nsresult rv = mCacheDirectory->Clone(getter_AddRefs(mStoreDirectory));
NS_ENSURE_SUCCESS(rv, rv);
rv = mStoreDirectory->AppendNative(STORE_DIRECTORY);
NS_ENSURE_SUCCESS(rv, rv);
// Make sure LookupCaches (which are persistent and survive updates)
// are reading/writing in the right place. We will be moving their
// files "underneath" them during backup/restore.
for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
mLookupCaches[i]->UpdateDirHandle(mStoreDirectory);
}
// Directory where to move a backup before an update.
rv = mCacheDirectory->Clone(getter_AddRefs(mBackupDirectory));
NS_ENSURE_SUCCESS(rv, rv);
rv = mBackupDirectory->AppendNative(STORE_DIRECTORY + BACKUP_DIR_SUFFIX);
NS_ENSURE_SUCCESS(rv, rv);
// Directory where to move the backup so we can atomically
// delete (really move) it.
rv = mCacheDirectory->Clone(getter_AddRefs(mToDeleteDirectory));
NS_ENSURE_SUCCESS(rv, rv);
rv = mToDeleteDirectory->AppendNative(STORE_DIRECTORY + TO_DELETE_DIR_SUFFIX);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Classifier::CreateStoreDirectory()
{
// Ensure the safebrowsing directory exists.
bool storeExists;
nsresult rv = mStoreDirectory->Exists(&storeExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!storeExists) {
rv = mStoreDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
NS_ENSURE_SUCCESS(rv, rv);
} else {
bool storeIsDir;
rv = mStoreDirectory->IsDirectory(&storeIsDir);
NS_ENSURE_SUCCESS(rv, rv);
if (!storeIsDir)
return NS_ERROR_FILE_DESTINATION_NOT_DIR;
}
return NS_OK;
}
nsresult
Classifier::Open(nsIFile& aCacheDirectory)
{
// Remember the Local profile directory.
nsresult rv = aCacheDirectory.Clone(getter_AddRefs(mCacheDirectory));
NS_ENSURE_SUCCESS(rv, rv);
// Create the handles to the update and backup directories.
rv = SetupPathNames();
NS_ENSURE_SUCCESS(rv, rv);
// Clean up any to-delete directories that haven't been deleted yet.
rv = CleanToDelete();
NS_ENSURE_SUCCESS(rv, rv);
// Check whether we have an incomplete update and recover from the
// backup if so.
rv = RecoverBackups();
NS_ENSURE_SUCCESS(rv, rv);
// Make sure the main store directory exists.
rv = CreateStoreDirectory();
NS_ENSURE_SUCCESS(rv, rv);
mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Build the list of know urlclassifier lists
// XXX: Disk IO potentially on the main thread during startup
RegenActiveTables();
return NS_OK;
}
void
Classifier::Close()
{
DropStores();
}
void
Classifier::Reset()
{
DropStores();
mStoreDirectory->Remove(true);
mBackupDirectory->Remove(true);
mToDeleteDirectory->Remove(true);
CreateStoreDirectory();
mTableFreshness.Clear();
RegenActiveTables();
}
void
Classifier::TableRequest(nsACString& aResult)
{
nsTArray<nsCString> tables;
ActiveTables(tables);
for (uint32_t i = 0; i < tables.Length(); i++) {
HashStore store(tables[i], mStoreDirectory);
nsresult rv = store.Open();
if (NS_FAILED(rv))
continue;
aResult.Append(store.TableName());
aResult.Append(';');
ChunkSet &adds = store.AddChunks();
ChunkSet &subs = store.SubChunks();
if (adds.Length() > 0) {
aResult.AppendLiteral("a:");
nsAutoCString addList;
adds.Serialize(addList);
aResult.Append(addList);
}
if (subs.Length() > 0) {
if (adds.Length() > 0)
aResult.Append(':');
aResult.AppendLiteral("s:");
nsAutoCString subList;
subs.Serialize(subList);
aResult.Append(subList);
}
aResult.Append('\n');
}
}
nsresult
Classifier::Check(const nsACString& aSpec,
const nsACString& aTables,
uint32_t aFreshnessGuarantee,
LookupResultArray& aResults)
{
Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_CHECK_TIME> timer;
// Get the set of fragments based on the url. This is necessary because we
// only look up at most 5 URLs per aSpec, even if aSpec has more than 5
// components.
nsTArray<nsCString> fragments;
nsresult rv = LookupCache::GetLookupFragments(aSpec, &fragments);
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<nsCString> activeTables;
SplitTables(aTables, activeTables);
nsTArray<LookupCache*> cacheArray;
for (uint32_t i = 0; i < activeTables.Length(); i++) {
LOG(("Checking table %s", activeTables[i].get()));
LookupCache *cache = GetLookupCache(activeTables[i]);
if (cache) {
cacheArray.AppendElement(cache);
} else {
return NS_ERROR_FAILURE;
}
}
// Now check each lookup fragment against the entries in the DB.
for (uint32_t i = 0; i < fragments.Length(); i++) {
Completion lookupHash;
lookupHash.FromPlaintext(fragments[i], mCryptoHash);
// Get list of host keys to look up
Completion hostKey;
rv = LookupCache::GetKey(fragments[i], &hostKey, mCryptoHash);
if (NS_FAILED(rv)) {
// Local host on the network.
continue;
}
#if DEBUG && defined(PR_LOGGING)
if (LOG_ENABLED()) {
nsAutoCString checking;
lookupHash.ToHexString(checking);
LOG(("Checking fragment %s, hash %s (%X)", fragments[i].get(),
checking.get(), lookupHash.ToUint32()));
}
#endif
for (uint32_t i = 0; i < cacheArray.Length(); i++) {
LookupCache *cache = cacheArray[i];
bool has, complete;
rv = cache->Has(lookupHash, &has, &complete);
NS_ENSURE_SUCCESS(rv, rv);
if (has) {
LookupResult *result = aResults.AppendElement();
if (!result)
return NS_ERROR_OUT_OF_MEMORY;
int64_t age;
bool found = mTableFreshness.Get(cache->TableName(), &age);
if (!found) {
age = 24 * 60 * 60; // just a large number
} else {
int64_t now = (PR_Now() / PR_USEC_PER_SEC);
age = now - age;
}
LOG(("Found a result in %s: %s (Age: %Lds)",
cache->TableName().get(),
complete ? "complete." : "Not complete.",
age));
result->hash.complete = lookupHash;
result->mComplete = complete;
result->mFresh = (age < aFreshnessGuarantee);
result->mTableName.Assign(cache->TableName());
}
}
}
return NS_OK;
}
nsresult
Classifier::ApplyUpdates(nsTArray<TableUpdate*>* aUpdates)
{
Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_UPDATE_TIME> timer;
#if defined(PR_LOGGING)
PRIntervalTime clockStart = 0;
if (LOG_ENABLED() || true) {
clockStart = PR_IntervalNow();
}
#endif
LOG(("Backup before update."));
nsresult rv = BackupTables();
NS_ENSURE_SUCCESS(rv, rv);
LOG(("Applying %d table updates.", aUpdates->Length()));
for (uint32_t i = 0; i < aUpdates->Length(); i++) {
// Previous ApplyTableUpdates() may have consumed this update..
if ((*aUpdates)[i]) {
// Run all updates for one table
nsCString updateTable(aUpdates->ElementAt(i)->TableName());
rv = ApplyTableUpdates(aUpdates, updateTable);
if (NS_FAILED(rv)) {
if (rv != NS_ERROR_OUT_OF_MEMORY) {
Reset();
}
return rv;
}
}
}
aUpdates->Clear();
rv = RegenActiveTables();
NS_ENSURE_SUCCESS(rv, rv);
LOG(("Cleaning up backups."));
// Move the backup directory away (signaling the transaction finished
// successfully). This is atomic.
rv = RemoveBackupTables();
NS_ENSURE_SUCCESS(rv, rv);
// Do the actual deletion of the backup files.
rv = CleanToDelete();
NS_ENSURE_SUCCESS(rv, rv);
LOG(("Done applying updates."));
#if defined(PR_LOGGING)
if (LOG_ENABLED() || true) {
PRIntervalTime clockEnd = PR_IntervalNow();
LOG(("update took %dms\n",
PR_IntervalToMilliseconds(clockEnd - clockStart)));
}
#endif
return NS_OK;
}
nsresult
Classifier::MarkSpoiled(nsTArray<nsCString>& aTables)
{
for (uint32_t i = 0; i < aTables.Length(); i++) {
LOG(("Spoiling table: %s", aTables[i].get()));
// Spoil this table by marking it as no known freshness
mTableFreshness.Remove(aTables[i]);
// Remove any cached Completes for this table
LookupCache *cache = GetLookupCache(aTables[i]);
if (cache) {
cache->ClearCompleteCache();
}
}
return NS_OK;
}
void
Classifier::DropStores()
{
for (uint32_t i = 0; i < mHashStores.Length(); i++) {
delete mHashStores[i];
}
mHashStores.Clear();
for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
delete mLookupCaches[i];
}
mLookupCaches.Clear();
}
nsresult
Classifier::RegenActiveTables()
{
mActiveTablesCache.Clear();
nsTArray<nsCString> foundTables;
ScanStoreDir(foundTables);
for (uint32_t i = 0; i < foundTables.Length(); i++) {
nsCString table(foundTables[i]);
HashStore store(table, mStoreDirectory);
nsresult rv = store.Open();
if (NS_FAILED(rv))
continue;
LookupCache *lookupCache = GetLookupCache(store.TableName());
if (!lookupCache) {
continue;
}
if (!lookupCache->IsPrimed())
continue;
const ChunkSet &adds = store.AddChunks();
const ChunkSet &subs = store.SubChunks();
if (adds.Length() == 0 && subs.Length() == 0)
continue;
LOG(("Active table: %s", store.TableName().get()));
mActiveTablesCache.AppendElement(store.TableName());
}
return NS_OK;
}
nsresult
Classifier::ScanStoreDir(nsTArray<nsCString>& aTables)
{
nsCOMPtr<nsISimpleEnumerator> entries;
nsresult rv = mStoreDirectory->GetDirectoryEntries(getter_AddRefs(entries));
NS_ENSURE_SUCCESS(rv, rv);
bool hasMore;
while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> supports;
rv = entries->GetNext(getter_AddRefs(supports));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
nsCString leafName;
rv = file->GetNativeLeafName(leafName);
NS_ENSURE_SUCCESS(rv, rv);
nsCString suffix(NS_LITERAL_CSTRING(".sbstore"));
int32_t dot = leafName.RFind(suffix, 0);
if (dot != -1) {
leafName.Cut(dot, suffix.Length());
aTables.AppendElement(leafName);
}
}
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Classifier::ActiveTables(nsTArray<nsCString>& aTables)
{
aTables = mActiveTablesCache;
return NS_OK;
}
nsresult
Classifier::CleanToDelete()
{
bool exists;
nsresult rv = mToDeleteDirectory->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (exists) {
rv = mToDeleteDirectory->Remove(true);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
Classifier::BackupTables()
{
// We have to work in reverse here: first move the normal directory
// away to be the backup directory, then copy the files over
// to the normal directory. This ensures that if we crash the backup
// dir always has a valid, complete copy, instead of a partial one,
// because that's the one we will copy over the normal store dir.
nsCString backupDirName;
nsresult rv = mBackupDirectory->GetNativeLeafName(backupDirName);
NS_ENSURE_SUCCESS(rv, rv);
nsCString storeDirName;
rv = mStoreDirectory->GetNativeLeafName(storeDirName);
NS_ENSURE_SUCCESS(rv, rv);
rv = mStoreDirectory->MoveToNative(nullptr, backupDirName);
NS_ENSURE_SUCCESS(rv, rv);
rv = mStoreDirectory->CopyToNative(nullptr, storeDirName);
NS_ENSURE_SUCCESS(rv, rv);
// We moved some things to new places, so move the handles around, too.
rv = SetupPathNames();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Classifier::RemoveBackupTables()
{
nsCString toDeleteName;
nsresult rv = mToDeleteDirectory->GetNativeLeafName(toDeleteName);
NS_ENSURE_SUCCESS(rv, rv);
rv = mBackupDirectory->MoveToNative(nullptr, toDeleteName);
NS_ENSURE_SUCCESS(rv, rv);
// mBackupDirectory now points to toDelete, fix that up.
rv = SetupPathNames();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Classifier::RecoverBackups()
{
bool backupExists;
nsresult rv = mBackupDirectory->Exists(&backupExists);
NS_ENSURE_SUCCESS(rv, rv);
if (backupExists) {
// Remove the safebrowsing dir if it exists
nsCString storeDirName;
rv = mStoreDirectory->GetNativeLeafName(storeDirName);
NS_ENSURE_SUCCESS(rv, rv);
bool storeExists;
rv = mStoreDirectory->Exists(&storeExists);
NS_ENSURE_SUCCESS(rv, rv);
if (storeExists) {
rv = mStoreDirectory->Remove(true);
NS_ENSURE_SUCCESS(rv, rv);
}
// Move the backup to the store location
rv = mBackupDirectory->MoveToNative(nullptr, storeDirName);
NS_ENSURE_SUCCESS(rv, rv);
// mBackupDirectory now points to storeDir, fix up.
rv = SetupPathNames();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
/*
* This will consume+delete updates from the passed nsTArray.
*/
nsresult
Classifier::ApplyTableUpdates(nsTArray<TableUpdate*>* aUpdates,
const nsACString& aTable)
{
LOG(("Classifier::ApplyTableUpdates(%s)", PromiseFlatCString(aTable).get()));
HashStore store(aTable, mStoreDirectory);
// take the quick exit if there is no valid update for us
// (common case)
uint32_t validupdates = 0;
for (uint32_t i = 0; i < aUpdates->Length(); i++) {
TableUpdate *update = aUpdates->ElementAt(i);
if (!update || !update->TableName().Equals(store.TableName()))
continue;
if (update->Empty()) {
aUpdates->ElementAt(i) = nullptr;
delete update;
continue;
}
validupdates++;
}
if (!validupdates) {
// This can happen if the update was only valid for one table.
return NS_OK;
}
nsresult rv = store.Open();
NS_ENSURE_SUCCESS(rv, rv);
rv = store.BeginUpdate();
NS_ENSURE_SUCCESS(rv, rv);
// Read the part of the store that is (only) in the cache
LookupCache *prefixSet = GetLookupCache(store.TableName());
if (!prefixSet) {
return NS_ERROR_FAILURE;
}
FallibleTArray<uint32_t> AddPrefixHashes;
rv = prefixSet->GetPrefixes(AddPrefixHashes);
NS_ENSURE_SUCCESS(rv, rv);
rv = store.AugmentAdds(AddPrefixHashes);
NS_ENSURE_SUCCESS(rv, rv);
AddPrefixHashes.Clear();
uint32_t applied = 0;
bool updateFreshness = false;
bool hasCompletes = false;
for (uint32_t i = 0; i < aUpdates->Length(); i++) {
TableUpdate *update = aUpdates->ElementAt(i);
if (!update || !update->TableName().Equals(store.TableName()))
continue;
rv = store.ApplyUpdate(*update);
NS_ENSURE_SUCCESS(rv, rv);
applied++;
LOG(("Applied update to table %s:", store.TableName().get()));
LOG((" %d add chunks", update->AddChunks().Length()));
LOG((" %d add prefixes", update->AddPrefixes().Length()));
LOG((" %d add completions", update->AddCompletes().Length()));
LOG((" %d sub chunks", update->SubChunks().Length()));
LOG((" %d sub prefixes", update->SubPrefixes().Length()));
LOG((" %d sub completions", update->SubCompletes().Length()));
LOG((" %d add expirations", update->AddExpirations().Length()));
LOG((" %d sub expirations", update->SubExpirations().Length()));
if (!update->IsLocalUpdate()) {
updateFreshness = true;
LOG(("Remote update, updating freshness"));
}
if (update->AddCompletes().Length() > 0
|| update->SubCompletes().Length() > 0) {
hasCompletes = true;
LOG(("Contains Completes, keeping cache."));
}
aUpdates->ElementAt(i) = nullptr;
delete update;
}
LOG(("Applied %d update(s) to %s.", applied, store.TableName().get()));
rv = store.Rebuild();
NS_ENSURE_SUCCESS(rv, rv);
// Not an update with Completes, clear all completes data.
if (!hasCompletes) {
store.ClearCompletes();
}
LOG(("Table %s now has:", store.TableName().get()));
LOG((" %d add chunks", store.AddChunks().Length()));
LOG((" %d add prefixes", store.AddPrefixes().Length()));
LOG((" %d add completions", store.AddCompletes().Length()));
LOG((" %d sub chunks", store.SubChunks().Length()));
LOG((" %d sub prefixes", store.SubPrefixes().Length()));
LOG((" %d sub completions", store.SubCompletes().Length()));
rv = store.WriteFile();
NS_ENSURE_SUCCESS(rv, rv);
// At this point the store is updated and written out to disk, but
// the data is still in memory. Build our quick-lookup table here.
rv = prefixSet->Build(store.AddPrefixes(), store.AddCompletes());
NS_ENSURE_SUCCESS(rv, rv);
#if defined(DEBUG) && defined(PR_LOGGING)
prefixSet->Dump();
#endif
rv = prefixSet->WriteFile();
NS_ENSURE_SUCCESS(rv, rv);
if (updateFreshness) {
int64_t now = (PR_Now() / PR_USEC_PER_SEC);
LOG(("Successfully updated %s", store.TableName().get()));
mTableFreshness.Put(store.TableName(), now);
}
return NS_OK;
}
LookupCache *
Classifier::GetLookupCache(const nsACString& aTable)
{
for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
if (mLookupCaches[i]->TableName().Equals(aTable)) {
return mLookupCaches[i];
}
}
LookupCache *cache = new LookupCache(aTable, mStoreDirectory);
nsresult rv = cache->Init();
if (NS_FAILED(rv)) {
return nullptr;
}
rv = cache->Open();
if (NS_FAILED(rv)) {
if (rv == NS_ERROR_FILE_CORRUPTED) {
Reset();
}
return nullptr;
}
mLookupCaches.AppendElement(cache);
return cache;
}
nsresult
Classifier::ReadNoiseEntries(const Prefix& aPrefix,
const nsACString& aTableName,
uint32_t aCount,
PrefixArray* aNoiseEntries)
{
LookupCache *cache = GetLookupCache(aTableName);
if (!cache) {
return NS_ERROR_FAILURE;
}
FallibleTArray<uint32_t> prefixes;
nsresult rv = cache->GetPrefixes(prefixes);
NS_ENSURE_SUCCESS(rv, rv);
size_t idx = prefixes.BinaryIndexOf(aPrefix.ToUint32());
if (idx == nsTArray<uint32_t>::NoIndex) {
NS_WARNING("Could not find prefix in PrefixSet during noise lookup");
return NS_ERROR_FAILURE;
}
idx -= idx % aCount;
for (size_t i = 0; (i < aCount) && ((idx+i) < prefixes.Length()); i++) {
Prefix newPref;
newPref.FromUint32(prefixes[idx+i]);
if (newPref != aPrefix) {
aNoiseEntries->AppendElement(newPref);
}
}
return NS_OK;
}
} // namespace safebrowsing
} // namespace mozilla
@@ -1,110 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef Classifier_h__
#define Classifier_h__
#include "Entries.h"
#include "HashStore.h"
#include "ProtocolParser.h"
#include "LookupCache.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIFile.h"
#include "nsICryptoHash.h"
#include "nsDataHashtable.h"
namespace mozilla {
namespace safebrowsing {
/**
* Maintains the stores and LookupCaches for the url classifier.
*/
class Classifier {
public:
Classifier();
~Classifier();
nsresult Open(nsIFile& aCacheDirectory);
void Close();
void Reset();
/**
* Get the list of active tables and their chunks in a format
* suitable for an update request.
*/
void TableRequest(nsACString& aResult);
/*
* Get all tables that we know about.
*/
nsresult ActiveTables(nsTArray<nsCString>& aTables);
/**
* Check a URL against the specified tables.
*/
nsresult Check(const nsACString& aSpec,
const nsACString& tables,
uint32_t aFreshnessGuarantee,
LookupResultArray& aResults);
/**
* Apply the table updates in the array. Takes ownership of
* the updates in the array and clears it. Wacky!
*/
nsresult ApplyUpdates(nsTArray<TableUpdate*>* aUpdates);
/**
* Failed update. Spoil the entries so we don't block hosts
* unnecessarily
*/
nsresult MarkSpoiled(nsTArray<nsCString>& aTables);
nsresult CacheCompletions(const CacheResultArray& aResults);
uint32_t GetHashKey(void) { return mHashKey; }
/*
* Get a bunch of extra prefixes to query for completion
* and mask the real entry being requested
*/
nsresult ReadNoiseEntries(const Prefix& aPrefix,
const nsACString& aTableName,
uint32_t aCount,
PrefixArray* aNoiseEntries);
static void SplitTables(const nsACString& str, nsTArray<nsCString>& tables);
private:
void DropStores();
nsresult CreateStoreDirectory();
nsresult SetupPathNames();
nsresult RecoverBackups();
nsresult CleanToDelete();
nsresult BackupTables();
nsresult RemoveBackupTables();
nsresult RegenActiveTables();
nsresult ScanStoreDir(nsTArray<nsCString>& aTables);
nsresult ApplyTableUpdates(nsTArray<TableUpdate*>* aUpdates,
const nsACString& aTable);
LookupCache *GetLookupCache(const nsACString& aTable);
// Root dir of the Local profile.
nsCOMPtr<nsIFile> mCacheDirectory;
// Main directory where to store the databases.
nsCOMPtr<nsIFile> mStoreDirectory;
// Used for atomically updating the other dirs.
nsCOMPtr<nsIFile> mBackupDirectory;
nsCOMPtr<nsIFile> mToDeleteDirectory;
nsCOMPtr<nsICryptoHash> mCryptoHash;
nsTArray<HashStore*> mHashStores;
nsTArray<LookupCache*> mLookupCaches;
nsTArray<nsCString> mActiveTablesCache;
uint32_t mHashKey;
// Stores the last time a given table was updated (seconds).
nsDataHashtable<nsCStringHashKey, int64_t> mTableFreshness;
};
}
}
#endif
-314
View File
@@ -1,314 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This header file defines the storage types of the actual safebrowsing
// chunk data, which may be either 32-bit hashes or complete 256-bit hashes.
// Chunk numbers are represented in ChunkSet.h.
#ifndef SBEntries_h__
#define SBEntries_h__
#include "nsTArray.h"
#include "nsString.h"
#include "nsICryptoHash.h"
#include "nsNetUtil.h"
#if DEBUG
#include "plbase64.h"
#endif
namespace mozilla {
namespace safebrowsing {
#define PREFIX_SIZE 4
#define COMPLETE_SIZE 32
// This is the struct that contains 4-byte hash prefixes.
template <uint32_t S, class Comparator>
struct SafebrowsingHash
{
static const uint32_t sHashSize = S;
typedef SafebrowsingHash<S, Comparator> self_type;
uint8_t buf[S];
nsresult FromPlaintext(const nsACString& aPlainText, nsICryptoHash* aHash) {
// From the protocol doc:
// Each entry in the chunk is composed
// of the SHA 256 hash of a suffix/prefix expression.
nsresult rv = aHash->Init(nsICryptoHash::SHA256);
NS_ENSURE_SUCCESS(rv, rv);
rv = aHash->Update
(reinterpret_cast<const uint8_t*>(aPlainText.BeginReading()),
aPlainText.Length());
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString hashed;
rv = aHash->Finish(false, hashed);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(hashed.Length() >= sHashSize,
"not enough characters in the hash");
memcpy(buf, hashed.BeginReading(), sHashSize);
return NS_OK;
}
void Assign(const nsACString& aStr) {
NS_ASSERTION(aStr.Length() >= sHashSize,
"string must be at least sHashSize characters long");
memcpy(buf, aStr.BeginReading(), sHashSize);
}
int Compare(const self_type& aOther) const {
return Comparator::Compare(buf, aOther.buf);
}
bool operator==(const self_type& aOther) const {
return Comparator::Compare(buf, aOther.buf) == 0;
}
bool operator!=(const self_type& aOther) const {
return Comparator::Compare(buf, aOther.buf) != 0;
}
bool operator<(const self_type& aOther) const {
return Comparator::Compare(buf, aOther.buf) < 0;
}
#ifdef DEBUG
void ToString(nsACString& aStr) const {
uint32_t len = ((sHashSize + 2) / 3) * 4;
aStr.SetCapacity(len + 1);
PL_Base64Encode((char*)buf, sHashSize, aStr.BeginWriting());
aStr.BeginWriting()[len] = '\0';
}
void ToHexString(nsACString& aStr) const {
static const char* const lut = "0123456789ABCDEF";
// 32 bytes is the longest hash
size_t len = 32;
aStr.SetCapacity(2 * len);
for (size_t i = 0; i < len; ++i) {
const char c = static_cast<const char>(buf[i]);
aStr.Append(lut[(c >> 4) & 0x0F]);
aStr.Append(lut[c & 15]);
}
}
#endif
uint32_t ToUint32() const {
return *((uint32_t*)buf);
}
void FromUint32(uint32_t aHash) {
*((uint32_t*)buf) = aHash;
}
};
class PrefixComparator {
public:
static int Compare(const uint8_t* a, const uint8_t* b) {
uint32_t first = *((uint32_t*)a);
uint32_t second = *((uint32_t*)b);
if (first > second) {
return 1;
} else if (first == second) {
return 0;
} else {
return -1;
}
}
};
// Use this for 4-byte hashes
typedef SafebrowsingHash<PREFIX_SIZE, PrefixComparator> Prefix;
typedef nsTArray<Prefix> PrefixArray;
class CompletionComparator {
public:
static int Compare(const uint8_t* a, const uint8_t* b) {
return memcmp(a, b, COMPLETE_SIZE);
}
};
// Use this for 32-byte hashes
typedef SafebrowsingHash<COMPLETE_SIZE, CompletionComparator> Completion;
typedef nsTArray<Completion> CompletionArray;
struct AddPrefix {
// The truncated hash.
Prefix prefix;
// The chunk number to which it belongs.
uint32_t addChunk;
AddPrefix() : addChunk(0) {}
// Returns the chunk number.
uint32_t Chunk() const { return addChunk; }
const Prefix &PrefixHash() const { return prefix; }
template<class T>
int Compare(const T& other) const {
int cmp = prefix.Compare(other.PrefixHash());
if (cmp != 0) {
return cmp;
}
return addChunk - other.addChunk;
}
};
struct AddComplete {
Completion complete;
uint32_t addChunk;
AddComplete() : addChunk(0) {}
uint32_t Chunk() const { return addChunk; }
// The 4-byte prefix of the sha256 hash.
uint32_t ToUint32() const { return complete.ToUint32(); }
// The 32-byte sha256 hash.
const Completion &CompleteHash() const { return complete; }
template<class T>
int Compare(const T& other) const {
int cmp = complete.Compare(other.CompleteHash());
if (cmp != 0) {
return cmp;
}
return addChunk - other.addChunk;
}
};
struct SubPrefix {
// The hash to subtract.
Prefix prefix;
// The chunk number of the add chunk to which the hash belonged.
uint32_t addChunk;
// The chunk number of this sub chunk.
uint32_t subChunk;
SubPrefix(): addChunk(0), subChunk(0) {}
uint32_t Chunk() const { return subChunk; }
uint32_t AddChunk() const { return addChunk; }
const Prefix &PrefixHash() const { return prefix; }
template<class T>
// Returns 0 if and only if the chunks are the same in every way.
int Compare(const T& aOther) const {
int cmp = prefix.Compare(aOther.PrefixHash());
if (cmp != 0)
return cmp;
if (addChunk != aOther.addChunk)
return addChunk - aOther.addChunk;
return subChunk - aOther.subChunk;
}
template<class T>
int CompareAlt(const T& aOther) const {
Prefix other;
other.FromUint32(aOther.ToUint32());
int cmp = prefix.Compare(other);
if (cmp != 0)
return cmp;
return addChunk - aOther.addChunk;
}
};
struct SubComplete {
Completion complete;
uint32_t addChunk;
uint32_t subChunk;
SubComplete() : addChunk(0), subChunk(0) {}
uint32_t Chunk() const { return subChunk; }
uint32_t AddChunk() const { return addChunk; }
const Completion &CompleteHash() const { return complete; }
// The 4-byte prefix of the sha256 hash.
uint32_t ToUint32() const { return complete.ToUint32(); }
int Compare(const SubComplete& aOther) const {
int cmp = complete.Compare(aOther.complete);
if (cmp != 0)
return cmp;
if (addChunk != aOther.addChunk)
return addChunk - aOther.addChunk;
return subChunk - aOther.subChunk;
}
};
typedef FallibleTArray<AddPrefix> AddPrefixArray;
typedef FallibleTArray<AddComplete> AddCompleteArray;
typedef FallibleTArray<SubPrefix> SubPrefixArray;
typedef FallibleTArray<SubComplete> SubCompleteArray;
/**
* Compares chunks by their add chunk, then their prefix.
*/
template<class T>
class EntryCompare {
public:
typedef T elem_type;
static int Compare(const void* e1, const void* e2) {
const elem_type* a = static_cast<const elem_type*>(e1);
const elem_type* b = static_cast<const elem_type*>(e2);
return a->Compare(*b);
}
};
/**
* Sort an array of store entries. nsTArray::Sort uses Equal/LessThan
* to sort, this does a single Compare so it's a bit quicker over the
* large sorts we do.
*/
template<class T, class Alloc>
void
EntrySort(nsTArray_Impl<T, Alloc>& aArray)
{
qsort(aArray.Elements(), aArray.Length(), sizeof(T),
EntryCompare<T>::Compare);
}
template<class T, class Alloc>
nsresult
ReadTArray(nsIInputStream* aStream, nsTArray_Impl<T, Alloc>* aArray, uint32_t aNumElements)
{
aArray->SetLength(aNumElements);
void *buffer = aArray->Elements();
nsresult rv = NS_ReadInputStreamToBuffer(aStream, &buffer,
(aNumElements * sizeof(T)));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
template<class T>
nsresult
ReadTArray(nsIInputStream* aStream, FallibleTArray<T>* aArray, uint32_t aNumElements)
{
if (!aArray->SetLength(aNumElements))
return NS_ERROR_OUT_OF_MEMORY;
void *buffer = aArray->Elements();
nsresult rv = NS_ReadInputStreamToBuffer(aStream, &buffer,
(aNumElements * sizeof(T)));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
template<class T, class Alloc>
nsresult
WriteTArray(nsIOutputStream* aStream, nsTArray_Impl<T, Alloc>& aArray)
{
uint32_t written;
return aStream->Write(reinterpret_cast<char*>(aArray.Elements()),
aArray.Length() * sizeof(T),
&written);
}
} // namespace safebrowsing
} // namespace mozilla
#endif // SBEntries_h__
File diff suppressed because it is too large Load Diff
@@ -1,200 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef HashStore_h__
#define HashStore_h__
#include "Entries.h"
#include "ChunkSet.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsCOMPtr.h"
namespace mozilla {
namespace safebrowsing {
// A table update is built from a single update chunk from the server. As the
// protocol parser processes each chunk, it constructs a table update with the
// new hashes.
class TableUpdate {
public:
explicit TableUpdate(const nsACString& aTable)
: mTable(aTable), mLocalUpdate(false) {}
const nsCString& TableName() const { return mTable; }
bool Empty() const {
return mAddChunks.Length() == 0 &&
mSubChunks.Length() == 0 &&
mAddExpirations.Length() == 0 &&
mSubExpirations.Length() == 0 &&
mAddPrefixes.Length() == 0 &&
mSubPrefixes.Length() == 0 &&
mAddCompletes.Length() == 0 &&
mSubCompletes.Length() == 0;
}
// Throughout, uint32_t aChunk refers only to the chunk number. Chunk data is
// stored in the Prefix structures.
void NewAddChunk(uint32_t aChunk) { mAddChunks.Set(aChunk); }
void NewSubChunk(uint32_t aChunk) { mSubChunks.Set(aChunk); }
void NewAddExpiration(uint32_t aChunk) { mAddExpirations.Set(aChunk); }
void NewSubExpiration(uint32_t aChunk) { mSubExpirations.Set(aChunk); }
void NewAddPrefix(uint32_t aAddChunk, const Prefix& aPrefix);
void NewSubPrefix(uint32_t aAddChunk, const Prefix& aPrefix, uint32_t aSubChunk);
void NewAddComplete(uint32_t aChunk, const Completion& aCompletion);
void NewSubComplete(uint32_t aAddChunk, const Completion& aCompletion,
uint32_t aSubChunk);
void SetLocalUpdate(void) { mLocalUpdate = true; }
bool IsLocalUpdate(void) { return mLocalUpdate; }
ChunkSet& AddChunks() { return mAddChunks; }
ChunkSet& SubChunks() { return mSubChunks; }
// Expirations for chunks.
ChunkSet& AddExpirations() { return mAddExpirations; }
ChunkSet& SubExpirations() { return mSubExpirations; }
// Hashes associated with this chunk.
AddPrefixArray& AddPrefixes() { return mAddPrefixes; }
SubPrefixArray& SubPrefixes() { return mSubPrefixes; }
AddCompleteArray& AddCompletes() { return mAddCompletes; }
SubCompleteArray& SubCompletes() { return mSubCompletes; }
private:
nsCString mTable;
// Update not from the remote server (no freshness)
bool mLocalUpdate;
// The list of chunk numbers that we have for each of the type of chunks.
ChunkSet mAddChunks;
ChunkSet mSubChunks;
ChunkSet mAddExpirations;
ChunkSet mSubExpirations;
// 4-byte sha256 prefixes.
AddPrefixArray mAddPrefixes;
SubPrefixArray mSubPrefixes;
// 32-byte hashes.
AddCompleteArray mAddCompletes;
SubCompleteArray mSubCompletes;
};
// There is one hash store per table.
class HashStore {
public:
HashStore(const nsACString& aTableName, nsIFile* aStoreFile);
~HashStore();
const nsCString& TableName() const { return mTableName; }
nsresult Open();
// Add Prefixes are stored partly in the PrefixSet (contains the
// Prefix data organized for fast lookup/low RAM usage) and partly in the
// HashStore (Add Chunk numbers - only used for updates, slow retrieval).
// AugmentAdds function joins the separate datasets into one complete
// prefixes+chunknumbers dataset.
nsresult AugmentAdds(const nsTArray<uint32_t>& aPrefixes);
ChunkSet& AddChunks() { return mAddChunks; }
ChunkSet& SubChunks() { return mSubChunks; }
AddPrefixArray& AddPrefixes() { return mAddPrefixes; }
AddCompleteArray& AddCompletes() { return mAddCompletes; }
SubPrefixArray& SubPrefixes() { return mSubPrefixes; }
SubCompleteArray& SubCompletes() { return mSubCompletes; }
// =======
// Updates
// =======
// Begin the update process. Reads the store into memory.
nsresult BeginUpdate();
// Imports the data from a TableUpdate.
nsresult ApplyUpdate(TableUpdate &aUpdate);
// Process expired chunks
nsresult Expire();
// Rebuild the store, Incorporating all the applied updates.
nsresult Rebuild();
// Write the current state of the store to disk.
// If you call between ApplyUpdate() and Rebuild(), you'll
// have a mess on your hands.
nsresult WriteFile();
// Wipe out all Completes.
void ClearCompletes();
private:
nsresult Reset();
nsresult ReadHeader();
nsresult SanityCheck();
nsresult CalculateChecksum(nsAutoCString& aChecksum, uint32_t aFileSize,
bool aChecksumPresent);
nsresult CheckChecksum(nsIFile* aStoreFile, uint32_t aFileSize);
void UpdateHeader();
nsresult ReadChunkNumbers();
nsresult ReadHashes();
nsresult ReadAddPrefixes();
nsresult ReadSubPrefixes();
nsresult WriteAddPrefixes(nsIOutputStream* aOut);
nsresult WriteSubPrefixes(nsIOutputStream* aOut);
nsresult ProcessSubs();
// This is used for checking that the database is correct and for figuring out
// the number of chunks, etc. to read from disk on restart.
struct Header {
uint32_t magic;
uint32_t version;
uint32_t numAddChunks;
uint32_t numSubChunks;
uint32_t numAddPrefixes;
uint32_t numSubPrefixes;
uint32_t numAddCompletes;
uint32_t numSubCompletes;
};
Header mHeader;
// The name of the table (must end in -shavar or -digest256, or evidently
// -simple for unittesting.
nsCString mTableName;
nsCOMPtr<nsIFile> mStoreDirectory;
bool mInUpdate;
nsCOMPtr<nsIInputStream> mInputStream;
// Chunk numbers, stored as uint32_t arrays.
ChunkSet mAddChunks;
ChunkSet mSubChunks;
ChunkSet mAddExpirations;
ChunkSet mSubExpirations;
// Chunk data for shavar tables. See Entries.h for format.
AddPrefixArray mAddPrefixes;
SubPrefixArray mSubPrefixes;
// See bug 806422 for background. We must be able to distinguish between
// updates from the completion server and updates from the regular server.
AddCompleteArray mAddCompletes;
SubCompleteArray mSubCompletes;
};
}
}
#endif
@@ -1,699 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LookupCache.h"
#include "HashStore.h"
#include "nsISeekableStream.h"
#include "mozilla/Telemetry.h"
#include "prlog.h"
#include "prprf.h"
// We act as the main entry point for all the real lookups,
// so note that those are not done to the actual HashStore.
// The latter solely exists to store the data needed to handle
// the updates from the protocol.
// This module has its own store, which stores the Completions,
// mostly caching lookups that have happened over the net.
// The prefixes are cached/checked by looking them up in the
// PrefixSet.
// Data format for the ".cache" files:
// uint32_t magic Identify the file type
// uint32_t version Version identifier for file format
// uint32_t numCompletions Amount of completions stored
// 0...numCompletions 256-bit Completions
// Name of the lookupcomplete cache
#define CACHE_SUFFIX ".cache"
// Name of the persistent PrefixSet storage
#define PREFIXSET_SUFFIX ".pset"
// NSPR_LOG_MODULES=UrlClassifierDbService:5
extern PRLogModuleInfo *gUrlClassifierDbServiceLog;
#if defined(PR_LOGGING)
#define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args)
#define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierDbServiceLog, 4)
#else
#define LOG(args)
#define LOG_ENABLED() (false)
#endif
namespace mozilla {
namespace safebrowsing {
const uint32_t LOOKUPCACHE_MAGIC = 0x1231af3e;
const uint32_t CURRENT_VERSION = 2;
LookupCache::LookupCache(const nsACString& aTableName, nsIFile* aStoreDir)
: mPrimed(false)
, mTableName(aTableName)
, mStoreDirectory(aStoreDir)
{
}
nsresult
LookupCache::Init()
{
mPrefixSet = new nsUrlClassifierPrefixSet();
nsresult rv = mPrefixSet->Init(mTableName);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
LookupCache::~LookupCache()
{
}
nsresult
LookupCache::Open()
{
nsCOMPtr<nsIFile> storeFile;
nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(CACHE_SUFFIX));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> inputStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), storeFile,
PR_RDONLY | nsIFile::OS_READAHEAD);
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
Reset();
return rv;
}
if (rv == NS_ERROR_FILE_NOT_FOUND) {
// Simply lacking a .cache file is a recoverable error,
// as unlike the .pset/.sbstore files it is a pure cache.
// Just create a new empty one.
ClearCompleteCache();
} else {
// Read in the .cache file
rv = ReadHeader(inputStream);
NS_ENSURE_SUCCESS(rv, rv);
LOG(("ReadCompletions"));
rv = ReadCompletions(inputStream);
NS_ENSURE_SUCCESS(rv, rv);
rv = inputStream->Close();
NS_ENSURE_SUCCESS(rv, rv);
}
LOG(("Loading PrefixSet"));
rv = LoadPrefixSet();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
LookupCache::UpdateDirHandle(nsIFile* aStoreDirectory)
{
return aStoreDirectory->Clone(getter_AddRefs(mStoreDirectory));
}
nsresult
LookupCache::Reset()
{
LOG(("LookupCache resetting"));
nsCOMPtr<nsIFile> storeFile;
nsCOMPtr<nsIFile> prefixsetFile;
nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = mStoreDirectory->Clone(getter_AddRefs(prefixsetFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(CACHE_SUFFIX));
NS_ENSURE_SUCCESS(rv, rv);
rv = prefixsetFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
NS_ENSURE_SUCCESS(rv, rv);
rv = storeFile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv);
rv = prefixsetFile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv);
ClearAll();
return NS_OK;
}
nsresult
LookupCache::Build(AddPrefixArray& aAddPrefixes,
AddCompleteArray& aAddCompletes)
{
Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LC_COMPLETIONS,
static_cast<uint32_t>(aAddCompletes.Length()));
mCompletions.Clear();
mCompletions.SetCapacity(aAddCompletes.Length());
for (uint32_t i = 0; i < aAddCompletes.Length(); i++) {
mCompletions.AppendElement(aAddCompletes[i].CompleteHash());
}
aAddCompletes.Clear();
mCompletions.Sort();
Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LC_PREFIXES,
static_cast<uint32_t>(aAddPrefixes.Length()));
nsresult rv = ConstructPrefixSet(aAddPrefixes);
NS_ENSURE_SUCCESS(rv, rv);
mPrimed = true;
return NS_OK;
}
#if defined(DEBUG) && defined(PR_LOGGING)
void
LookupCache::Dump()
{
if (!LOG_ENABLED())
return;
for (uint32_t i = 0; i < mCompletions.Length(); i++) {
nsAutoCString str;
mCompletions[i].ToHexString(str);
LOG(("Completion: %s", str.get()));
}
}
#endif
nsresult
LookupCache::Has(const Completion& aCompletion,
bool* aHas, bool* aComplete)
{
*aHas = *aComplete = false;
uint32_t prefix = aCompletion.ToUint32();
bool found;
nsresult rv = mPrefixSet->Contains(prefix, &found);
NS_ENSURE_SUCCESS(rv, rv);
LOG(("Probe in %s: %X, found %d", mTableName.get(), prefix, found));
if (found) {
*aHas = true;
}
if (mCompletions.BinaryIndexOf(aCompletion) != nsTArray<Completion>::NoIndex) {
LOG(("Complete in %s", mTableName.get()));
*aComplete = true;
*aHas = true;
}
return NS_OK;
}
nsresult
LookupCache::WriteFile()
{
nsCOMPtr<nsIFile> storeFile;
nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(CACHE_SUFFIX));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> out;
rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out), storeFile,
PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE);
NS_ENSURE_SUCCESS(rv, rv);
UpdateHeader();
LOG(("Writing %d completions", mHeader.numCompletions));
uint32_t written;
rv = out->Write(reinterpret_cast<char*>(&mHeader), sizeof(mHeader), &written);
NS_ENSURE_SUCCESS(rv, rv);
rv = WriteTArray(out, mCompletions);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISafeOutputStream> safeOut = do_QueryInterface(out);
rv = safeOut->Finish();
NS_ENSURE_SUCCESS(rv, rv);
rv = EnsureSizeConsistent();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> psFile;
rv = mStoreDirectory->Clone(getter_AddRefs(psFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = psFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
NS_ENSURE_SUCCESS(rv, rv);
rv = mPrefixSet->StoreToFile(psFile);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store the prefixset");
return NS_OK;
}
void
LookupCache::ClearAll()
{
ClearCompleteCache();
mPrefixSet->SetPrefixes(nullptr, 0);
mPrimed = false;
}
void
LookupCache::ClearCompleteCache()
{
mCompletions.Clear();
UpdateHeader();
}
void
LookupCache::UpdateHeader()
{
mHeader.magic = LOOKUPCACHE_MAGIC;
mHeader.version = CURRENT_VERSION;
mHeader.numCompletions = mCompletions.Length();
}
nsresult
LookupCache::EnsureSizeConsistent()
{
nsCOMPtr<nsIFile> storeFile;
nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(CACHE_SUFFIX));
NS_ENSURE_SUCCESS(rv, rv);
int64_t fileSize;
rv = storeFile->GetFileSize(&fileSize);
NS_ENSURE_SUCCESS(rv, rv);
if (fileSize < 0) {
return NS_ERROR_FAILURE;
}
int64_t expectedSize = sizeof(mHeader)
+ mHeader.numCompletions*sizeof(Completion);
if (expectedSize != fileSize) {
NS_WARNING("File length does not match. Probably corrupted.");
Reset();
return NS_ERROR_FILE_CORRUPTED;
}
return NS_OK;
}
nsresult
LookupCache::ReadHeader(nsIInputStream* aInputStream)
{
if (!aInputStream) {
ClearCompleteCache();
return NS_OK;
}
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aInputStream);
nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
NS_ENSURE_SUCCESS(rv, rv);
void *buffer = &mHeader;
rv = NS_ReadInputStreamToBuffer(aInputStream,
&buffer,
sizeof(Header));
NS_ENSURE_SUCCESS(rv, rv);
if (mHeader.magic != LOOKUPCACHE_MAGIC || mHeader.version != CURRENT_VERSION) {
NS_WARNING("Unexpected header data in the store.");
Reset();
return NS_ERROR_FILE_CORRUPTED;
}
LOG(("%d completions present", mHeader.numCompletions));
rv = EnsureSizeConsistent();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
LookupCache::ReadCompletions(nsIInputStream* aInputStream)
{
if (!mHeader.numCompletions) {
mCompletions.Clear();
return NS_OK;
}
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aInputStream);
nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, sizeof(Header));
NS_ENSURE_SUCCESS(rv, rv);
rv = ReadTArray(aInputStream, &mCompletions, mHeader.numCompletions);
NS_ENSURE_SUCCESS(rv, rv);
LOG(("Read %d completions", mCompletions.Length()));
return NS_OK;
}
/* static */ bool
LookupCache::IsCanonicalizedIP(const nsACString& aHost)
{
// The canonicalization process will have left IP addresses in dotted
// decimal with no surprises.
uint32_t i1, i2, i3, i4;
char c;
if (PR_sscanf(PromiseFlatCString(aHost).get(), "%u.%u.%u.%u%c",
&i1, &i2, &i3, &i4, &c) == 4) {
return (i1 <= 0xFF && i2 <= 0xFF && i3 <= 0xFF && i4 <= 0xFF);
}
return false;
}
/* static */ nsresult
LookupCache::GetKey(const nsACString& aSpec,
Completion* aHash,
nsCOMPtr<nsICryptoHash>& aCryptoHash)
{
nsACString::const_iterator begin, end, iter;
aSpec.BeginReading(begin);
aSpec.EndReading(end);
iter = begin;
if (!FindCharInReadable('/', iter, end)) {
return NS_OK;
}
const nsCSubstring& host = Substring(begin, iter);
if (IsCanonicalizedIP(host)) {
nsAutoCString key;
key.Assign(host);
key.Append('/');
return aHash->FromPlaintext(key, aCryptoHash);
}
nsTArray<nsCString> hostComponents;
ParseString(PromiseFlatCString(host), '.', hostComponents);
if (hostComponents.Length() < 2)
return NS_ERROR_FAILURE;
int32_t last = int32_t(hostComponents.Length()) - 1;
nsAutoCString lookupHost;
if (hostComponents.Length() > 2) {
lookupHost.Append(hostComponents[last - 2]);
lookupHost.Append('.');
}
lookupHost.Append(hostComponents[last - 1]);
lookupHost.Append('.');
lookupHost.Append(hostComponents[last]);
lookupHost.Append('/');
return aHash->FromPlaintext(lookupHost, aCryptoHash);
}
/* static */ nsresult
LookupCache::GetLookupFragments(const nsACString& aSpec,
nsTArray<nsCString>* aFragments)
{
aFragments->Clear();
nsACString::const_iterator begin, end, iter;
aSpec.BeginReading(begin);
aSpec.EndReading(end);
iter = begin;
if (!FindCharInReadable('/', iter, end)) {
return NS_OK;
}
const nsCSubstring& host = Substring(begin, iter++);
nsAutoCString path;
path.Assign(Substring(iter, end));
/**
* From the protocol doc:
* For the hostname, the client will try at most 5 different strings. They
* are:
* a) The exact hostname of the url
* b) The 4 hostnames formed by starting with the last 5 components and
* successivly removing the leading component. The top-level component
* can be skipped. This is not done if the hostname is a numerical IP.
*/
nsTArray<nsCString> hosts;
hosts.AppendElement(host);
if (!IsCanonicalizedIP(host)) {
host.BeginReading(begin);
host.EndReading(end);
int numHostComponents = 0;
while (RFindInReadable(NS_LITERAL_CSTRING("."), begin, end) &&
numHostComponents < MAX_HOST_COMPONENTS) {
// don't bother checking toplevel domains
if (++numHostComponents >= 2) {
host.EndReading(iter);
hosts.AppendElement(Substring(end, iter));
}
end = begin;
host.BeginReading(begin);
}
}
/**
* From the protocol doc:
* For the path, the client will also try at most 6 different strings.
* They are:
* a) the exact path of the url, including query parameters
* b) the exact path of the url, without query parameters
* c) the 4 paths formed by starting at the root (/) and
* successively appending path components, including a trailing
* slash. This behavior should only extend up to the next-to-last
* path component, that is, a trailing slash should never be
* appended that was not present in the original url.
*/
nsTArray<nsCString> paths;
nsAutoCString pathToAdd;
path.BeginReading(begin);
path.EndReading(end);
iter = begin;
if (FindCharInReadable('?', iter, end)) {
pathToAdd = Substring(begin, iter);
paths.AppendElement(pathToAdd);
end = iter;
}
int numPathComponents = 1;
iter = begin;
while (FindCharInReadable('/', iter, end) &&
numPathComponents < MAX_PATH_COMPONENTS) {
iter++;
pathToAdd.Assign(Substring(begin, iter));
paths.AppendElement(pathToAdd);
numPathComponents++;
}
// If we haven't already done so, add the full path
if (!pathToAdd.Equals(path)) {
paths.AppendElement(path);
}
// Check an empty path (for whole-domain blacklist entries)
paths.AppendElement(EmptyCString());
for (uint32_t hostIndex = 0; hostIndex < hosts.Length(); hostIndex++) {
for (uint32_t pathIndex = 0; pathIndex < paths.Length(); pathIndex++) {
nsCString key;
key.Assign(hosts[hostIndex]);
key.Append('/');
key.Append(paths[pathIndex]);
LOG(("Checking fragment %s", key.get()));
aFragments->AppendElement(key);
}
}
return NS_OK;
}
/* static */ nsresult
LookupCache::GetHostKeys(const nsACString& aSpec,
nsTArray<nsCString>* aHostKeys)
{
nsACString::const_iterator begin, end, iter;
aSpec.BeginReading(begin);
aSpec.EndReading(end);
iter = begin;
if (!FindCharInReadable('/', iter, end)) {
return NS_OK;
}
const nsCSubstring& host = Substring(begin, iter);
if (IsCanonicalizedIP(host)) {
nsCString *key = aHostKeys->AppendElement();
if (!key)
return NS_ERROR_OUT_OF_MEMORY;
key->Assign(host);
key->Append("/");
return NS_OK;
}
nsTArray<nsCString> hostComponents;
ParseString(PromiseFlatCString(host), '.', hostComponents);
if (hostComponents.Length() < 2) {
// no host or toplevel host, this won't match anything in the db
return NS_OK;
}
// First check with two domain components
int32_t last = int32_t(hostComponents.Length()) - 1;
nsCString *lookupHost = aHostKeys->AppendElement();
if (!lookupHost)
return NS_ERROR_OUT_OF_MEMORY;
lookupHost->Assign(hostComponents[last - 1]);
lookupHost->Append(".");
lookupHost->Append(hostComponents[last]);
lookupHost->Append("/");
// Now check with three domain components
if (hostComponents.Length() > 2) {
nsCString *lookupHost2 = aHostKeys->AppendElement();
if (!lookupHost2)
return NS_ERROR_OUT_OF_MEMORY;
lookupHost2->Assign(hostComponents[last - 2]);
lookupHost2->Append(".");
lookupHost2->Append(*lookupHost);
}
return NS_OK;
}
bool LookupCache::IsPrimed()
{
return mPrimed;
}
#ifdef DEBUG
template <class T>
static void EnsureSorted(T* aArray)
{
typename T::elem_type* start = aArray->Elements();
typename T::elem_type* end = aArray->Elements() + aArray->Length();
typename T::elem_type* iter = start;
typename T::elem_type* previous = start;
while (iter != end) {
previous = iter;
++iter;
if (iter != end) {
MOZ_ASSERT(*previous <= *iter);
}
}
return;
}
#endif
nsresult
LookupCache::ConstructPrefixSet(AddPrefixArray& aAddPrefixes)
{
Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_PS_CONSTRUCT_TIME> timer;
nsTArray<uint32_t> array;
array.SetCapacity(aAddPrefixes.Length());
for (uint32_t i = 0; i < aAddPrefixes.Length(); i++) {
array.AppendElement(aAddPrefixes[i].PrefixHash().ToUint32());
}
aAddPrefixes.Clear();
#ifdef DEBUG
// PrefixSet requires sorted order
EnsureSorted(&array);
#endif
// construct new one, replace old entries
nsresult rv = mPrefixSet->SetPrefixes(array.Elements(), array.Length());
if (NS_FAILED(rv)) {
goto error_bailout;
}
#ifdef DEBUG
uint32_t size;
size = mPrefixSet->SizeInMemory();
LOG(("SB tree done, size = %d bytes\n", size));
#endif
mPrimed = true;
return NS_OK;
error_bailout:
Telemetry::Accumulate(Telemetry::URLCLASSIFIER_PS_FAILURE, 1);
return rv;
}
nsresult
LookupCache::LoadPrefixSet()
{
nsCOMPtr<nsIFile> psFile;
nsresult rv = mStoreDirectory->Clone(getter_AddRefs(psFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = psFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
rv = psFile->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (exists) {
LOG(("stored PrefixSet exists, loading from disk"));
rv = mPrefixSet->LoadFromFile(psFile);
if (NS_FAILED(rv)) {
if (rv == NS_ERROR_FILE_CORRUPTED) {
Reset();
}
return rv;
}
mPrimed = true;
} else {
LOG(("no (usable) stored PrefixSet found"));
}
#ifdef DEBUG
if (mPrimed) {
uint32_t size = mPrefixSet->SizeInMemory();
LOG(("SB tree done, size = %d bytes\n", size));
}
#endif
return NS_OK;
}
nsresult
LookupCache::GetPrefixes(FallibleTArray<uint32_t>& aAddPrefixes)
{
if (!mPrimed) {
// This can happen if its a new table, so no error.
LOG(("GetPrefixes from empty LookupCache"));
return NS_OK;
}
return mPrefixSet->GetPrefixesNative(aAddPrefixes);
}
}
}
@@ -1,142 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LookupCache_h__
#define LookupCache_h__
#include "Entries.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsUrlClassifierPrefixSet.h"
#include "prlog.h"
namespace mozilla {
namespace safebrowsing {
#define MAX_HOST_COMPONENTS 5
#define MAX_PATH_COMPONENTS 4
class LookupResult {
public:
LookupResult() : mComplete(false), mNoise(false), mFresh(false), mProtocolConfirmed(false) {}
// The fragment that matched in the LookupCache
union {
Prefix prefix;
Completion complete;
} hash;
const Prefix &PrefixHash() { return hash.prefix; }
const Completion &CompleteHash() { return hash.complete; }
bool Confirmed() const { return (mComplete && mFresh) || mProtocolConfirmed; }
bool Complete() const { return mComplete; }
// True if we have a complete match for this hash in the table.
bool mComplete;
// True if this is a noise entry, i.e. an extra entry
// that is inserted to mask the true URL we are requesting
bool mNoise;
// True if we've updated this table recently-enough.
bool mFresh;
bool mProtocolConfirmed;
nsCString mTableName;
};
typedef nsTArray<LookupResult> LookupResultArray;
struct CacheResult {
AddComplete entry;
nsCString table;
};
typedef nsTArray<CacheResult> CacheResultArray;
class LookupCache {
public:
// Check for a canonicalized IP address.
static bool IsCanonicalizedIP(const nsACString& aHost);
// take a lookup string (www.hostname.com/path/to/resource.html) and
// expand it into the set of fragments that should be searched for in an
// entry
static nsresult GetLookupFragments(const nsACString& aSpec,
nsTArray<nsCString>* aFragments);
// Similar to GetKey(), but if the domain contains three or more components,
// two keys will be returned:
// hostname.com/foo/bar -> [hostname.com]
// mail.hostname.com/foo/bar -> [hostname.com, mail.hostname.com]
// www.mail.hostname.com/foo/bar -> [hostname.com, mail.hostname.com]
static nsresult GetHostKeys(const nsACString& aSpec,
nsTArray<nsCString>* aHostKeys);
// Get the database key for a given URI. This is the top three
// domain components if they exist, otherwise the top two.
// hostname.com/foo/bar -> hostname.com
// mail.hostname.com/foo/bar -> mail.hostname.com
// www.mail.hostname.com/foo/bar -> mail.hostname.com
static nsresult GetKey(const nsACString& aSpec, Completion* aHash,
nsCOMPtr<nsICryptoHash>& aCryptoHash);
LookupCache(const nsACString& aTableName, nsIFile* aStoreFile);
~LookupCache();
const nsCString &TableName() const { return mTableName; }
nsresult Init();
nsresult Open();
// The directory handle where we operate will
// be moved away when a backup is made.
nsresult UpdateDirHandle(nsIFile* aStoreDirectory);
// This will Clear() the passed arrays when done.
nsresult Build(AddPrefixArray& aAddPrefixes,
AddCompleteArray& aAddCompletes);
nsresult GetPrefixes(FallibleTArray<uint32_t>& aAddPrefixes);
void ClearCompleteCache();
#if DEBUG && defined(PR_LOGGING)
void Dump();
#endif
nsresult WriteFile();
nsresult Has(const Completion& aCompletion,
bool* aHas, bool* aComplete);
bool IsPrimed();
private:
void ClearAll();
nsresult Reset();
void UpdateHeader();
nsresult ReadHeader(nsIInputStream* aInputStream);
nsresult ReadCompletions(nsIInputStream* aInputStream);
nsresult EnsureSizeConsistent();
nsresult LoadPrefixSet();
// Construct a Prefix Set with known prefixes.
// This will Clear() aAddPrefixes when done.
nsresult ConstructPrefixSet(AddPrefixArray& aAddPrefixes);
struct Header {
uint32_t magic;
uint32_t version;
uint32_t numCompletions;
};
Header mHeader;
bool mPrimed;
nsCString mTableName;
nsCOMPtr<nsIFile> mStoreDirectory;
CompletionArray mCompletions;
// Set of prefixes known to be in the database
nsRefPtr<nsUrlClassifierPrefixSet> mPrefixSet;
};
}
}
#endif
@@ -1,632 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ProtocolParser.h"
#include "LookupCache.h"
#include "nsNetCID.h"
#include "prlog.h"
#include "prnetdb.h"
#include "prprf.h"
#include "nsUrlClassifierUtils.h"
// NSPR_LOG_MODULES=UrlClassifierDbService:5
extern PRLogModuleInfo *gUrlClassifierDbServiceLog;
#if defined(PR_LOGGING)
#define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args)
#define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierDbServiceLog, 4)
#else
#define LOG(args)
#define LOG_ENABLED() (false)
#endif
namespace mozilla {
namespace safebrowsing {
// Updates will fail if fed chunks larger than this
const uint32_t MAX_CHUNK_SIZE = (1024 * 1024);
const uint32_t DOMAIN_SIZE = 4;
// Parse one stringified range of chunks of the form "n" or "n-m" from a
// comma-separated list of chunks. Upon return, 'begin' will point to the
// next range of chunks in the list of chunks.
static bool
ParseChunkRange(nsACString::const_iterator& aBegin,
const nsACString::const_iterator& aEnd,
uint32_t* aFirst, uint32_t* aLast)
{
nsACString::const_iterator iter = aBegin;
FindCharInReadable(',', iter, aEnd);
nsAutoCString element(Substring(aBegin, iter));
aBegin = iter;
if (aBegin != aEnd)
aBegin++;
uint32_t numRead = PR_sscanf(element.get(), "%u-%u", aFirst, aLast);
if (numRead == 2) {
if (*aFirst > *aLast) {
uint32_t tmp = *aFirst;
*aFirst = *aLast;
*aLast = tmp;
}
return true;
}
if (numRead == 1) {
*aLast = *aFirst;
return true;
}
return false;
}
ProtocolParser::ProtocolParser()
: mState(PROTOCOL_STATE_CONTROL)
, mUpdateStatus(NS_OK)
, mUpdateWait(0)
, mResetRequested(false)
{
}
ProtocolParser::~ProtocolParser()
{
CleanupUpdates();
}
nsresult
ProtocolParser::Init(nsICryptoHash* aHasher)
{
mCryptoHash = aHasher;
return NS_OK;
}
void
ProtocolParser::SetCurrentTable(const nsACString& aTable)
{
mTableUpdate = GetTableUpdate(aTable);
}
nsresult
ProtocolParser::AppendStream(const nsACString& aData)
{
if (NS_FAILED(mUpdateStatus))
return mUpdateStatus;
nsresult rv;
mPending.Append(aData);
bool done = false;
while (!done) {
if (mState == PROTOCOL_STATE_CONTROL) {
rv = ProcessControl(&done);
} else if (mState == PROTOCOL_STATE_CHUNK) {
rv = ProcessChunk(&done);
} else {
NS_ERROR("Unexpected protocol state");
rv = NS_ERROR_FAILURE;
}
if (NS_FAILED(rv)) {
mUpdateStatus = rv;
return rv;
}
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessControl(bool* aDone)
{
nsresult rv;
nsAutoCString line;
*aDone = true;
while (NextLine(line)) {
//LOG(("Processing %s\n", line.get()));
if (StringBeginsWith(line, NS_LITERAL_CSTRING("i:"))) {
// Set the table name from the table header line.
SetCurrentTable(Substring(line, 2));
} else if (StringBeginsWith(line, NS_LITERAL_CSTRING("n:"))) {
if (PR_sscanf(line.get(), "n:%d", &mUpdateWait) != 1) {
LOG(("Error parsing n: '%s' (%d)", line.get(), mUpdateWait));
mUpdateWait = 0;
}
} else if (line.EqualsLiteral("r:pleasereset")) {
mResetRequested = true;
} else if (StringBeginsWith(line, NS_LITERAL_CSTRING("u:"))) {
rv = ProcessForward(line);
NS_ENSURE_SUCCESS(rv, rv);
} else if (StringBeginsWith(line, NS_LITERAL_CSTRING("a:")) ||
StringBeginsWith(line, NS_LITERAL_CSTRING("s:"))) {
rv = ProcessChunkControl(line);
NS_ENSURE_SUCCESS(rv, rv);
*aDone = false;
return NS_OK;
} else if (StringBeginsWith(line, NS_LITERAL_CSTRING("ad:")) ||
StringBeginsWith(line, NS_LITERAL_CSTRING("sd:"))) {
rv = ProcessExpirations(line);
NS_ENSURE_SUCCESS(rv, rv);
}
}
*aDone = true;
return NS_OK;
}
nsresult
ProtocolParser::ProcessExpirations(const nsCString& aLine)
{
if (!mTableUpdate) {
NS_WARNING("Got an expiration without a table.");
return NS_ERROR_FAILURE;
}
const nsCSubstring &list = Substring(aLine, 3);
nsACString::const_iterator begin, end;
list.BeginReading(begin);
list.EndReading(end);
while (begin != end) {
uint32_t first, last;
if (ParseChunkRange(begin, end, &first, &last)) {
for (uint32_t num = first; num <= last; num++) {
if (aLine[0] == 'a')
mTableUpdate->NewAddExpiration(num);
else
mTableUpdate->NewSubExpiration(num);
}
} else {
return NS_ERROR_FAILURE;
}
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessChunkControl(const nsCString& aLine)
{
if (!mTableUpdate) {
NS_WARNING("Got a chunk before getting a table.");
return NS_ERROR_FAILURE;
}
mState = PROTOCOL_STATE_CHUNK;
char command;
mChunkState.Clear();
if (PR_sscanf(aLine.get(),
"%c:%d:%d:%d",
&command,
&mChunkState.num, &mChunkState.hashSize, &mChunkState.length)
!= 4)
{
return NS_ERROR_FAILURE;
}
if (mChunkState.length > MAX_CHUNK_SIZE) {
return NS_ERROR_FAILURE;
}
if (!(mChunkState.hashSize == PREFIX_SIZE || mChunkState.hashSize == COMPLETE_SIZE)) {
NS_WARNING("Invalid hash size specified in update.");
return NS_ERROR_FAILURE;
}
if (StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-shavar")) ||
StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-simple"))) {
// Accommodate test tables ending in -simple for now.
mChunkState.type = (command == 'a') ? CHUNK_ADD : CHUNK_SUB;
} else if (StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-digest256"))) {
LOG(("Processing digest256 data"));
mChunkState.type = (command == 'a') ? CHUNK_ADD_DIGEST : CHUNK_SUB_DIGEST;
}
switch (mChunkState.type) {
case CHUNK_ADD:
mTableUpdate->NewAddChunk(mChunkState.num);
break;
case CHUNK_SUB:
mTableUpdate->NewSubChunk(mChunkState.num);
break;
case CHUNK_ADD_DIGEST:
mTableUpdate->NewAddChunk(mChunkState.num);
break;
case CHUNK_SUB_DIGEST:
mTableUpdate->NewSubChunk(mChunkState.num);
break;
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessForward(const nsCString& aLine)
{
const nsCSubstring &forward = Substring(aLine, 2);
return AddForward(forward);
}
nsresult
ProtocolParser::AddForward(const nsACString& aUrl)
{
if (!mTableUpdate) {
NS_WARNING("Forward without a table name.");
return NS_ERROR_FAILURE;
}
ForwardedUpdate *forward = mForwards.AppendElement();
forward->table = mTableUpdate->TableName();
forward->url.Assign(aUrl);
return NS_OK;
}
nsresult
ProtocolParser::ProcessChunk(bool* aDone)
{
if (!mTableUpdate) {
NS_WARNING("Processing chunk without an active table.");
return NS_ERROR_FAILURE;
}
NS_ASSERTION(mChunkState.num != 0, "Must have a chunk number.");
if (mPending.Length() < mChunkState.length) {
*aDone = true;
return NS_OK;
}
// Pull the chunk out of the pending stream data.
nsAutoCString chunk;
chunk.Assign(Substring(mPending, 0, mChunkState.length));
mPending.Cut(0, mChunkState.length);
*aDone = false;
mState = PROTOCOL_STATE_CONTROL;
//LOG(("Handling a %d-byte chunk", chunk.Length()));
if (StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-shavar"))) {
return ProcessShaChunk(chunk);
}
if (StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-digest256"))) {
return ProcessDigestChunk(chunk);
}
return ProcessPlaintextChunk(chunk);
}
/**
* Process a plaintext chunk (currently only used in unit tests).
*/
nsresult
ProtocolParser::ProcessPlaintextChunk(const nsACString& aChunk)
{
if (!mTableUpdate) {
NS_WARNING("Chunk received with no table.");
return NS_ERROR_FAILURE;
}
nsTArray<nsCString> lines;
ParseString(PromiseFlatCString(aChunk), '\n', lines);
// non-hashed tables need to be hashed
for (uint32_t i = 0; i < lines.Length(); i++) {
nsCString& line = lines[i];
if (mChunkState.type == CHUNK_ADD) {
if (mChunkState.hashSize == COMPLETE_SIZE) {
Completion hash;
hash.FromPlaintext(line, mCryptoHash);
mTableUpdate->NewAddComplete(mChunkState.num, hash);
} else {
NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
Prefix hash;
hash.FromPlaintext(line, mCryptoHash);
mTableUpdate->NewAddPrefix(mChunkState.num, hash);
}
} else {
nsCString::const_iterator begin, iter, end;
line.BeginReading(begin);
line.EndReading(end);
iter = begin;
uint32_t addChunk;
if (!FindCharInReadable(':', iter, end) ||
PR_sscanf(lines[i].get(), "%d:", &addChunk) != 1) {
NS_WARNING("Received sub chunk without associated add chunk.");
return NS_ERROR_FAILURE;
}
iter++;
if (mChunkState.hashSize == COMPLETE_SIZE) {
Completion hash;
hash.FromPlaintext(Substring(iter, end), mCryptoHash);
mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
} else {
NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
Prefix hash;
hash.FromPlaintext(Substring(iter, end), mCryptoHash);
mTableUpdate->NewSubPrefix(addChunk, hash, mChunkState.num);
}
}
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessShaChunk(const nsACString& aChunk)
{
uint32_t start = 0;
while (start < aChunk.Length()) {
// First four bytes are the domain key.
Prefix domain;
domain.Assign(Substring(aChunk, start, DOMAIN_SIZE));
start += DOMAIN_SIZE;
// Then a count of entries.
uint8_t numEntries = static_cast<uint8_t>(aChunk[start]);
start++;
nsresult rv;
if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == PREFIX_SIZE) {
rv = ProcessHostAdd(domain, numEntries, aChunk, &start);
} else if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == COMPLETE_SIZE) {
rv = ProcessHostAddComplete(numEntries, aChunk, &start);
} else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == PREFIX_SIZE) {
rv = ProcessHostSub(domain, numEntries, aChunk, &start);
} else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == COMPLETE_SIZE) {
rv = ProcessHostSubComplete(numEntries, aChunk, &start);
} else {
NS_WARNING("Unexpected chunk type/hash size!");
LOG(("Got an unexpected chunk type/hash size: %s:%d",
mChunkState.type == CHUNK_ADD ? "add" : "sub",
mChunkState.hashSize));
return NS_ERROR_FAILURE;
}
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessDigestChunk(const nsACString& aChunk)
{
if (mChunkState.type == CHUNK_ADD_DIGEST) {
return ProcessDigestAdd(aChunk);
}
if (mChunkState.type == CHUNK_SUB_DIGEST) {
return ProcessDigestSub(aChunk);
}
return NS_ERROR_UNEXPECTED;
}
nsresult
ProtocolParser::ProcessDigestAdd(const nsACString& aChunk)
{
// The ABNF format for add chunks is (HASH)+, where HASH is 32 bytes.
MOZ_ASSERT(aChunk.Length() % 32 == 0,
"Chunk length in bytes must be divisible by 4");
uint32_t start = 0;
while (start < aChunk.Length()) {
Completion hash;
hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
start += COMPLETE_SIZE;
mTableUpdate->NewAddComplete(mChunkState.num, hash);
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessDigestSub(const nsACString& aChunk)
{
// The ABNF format for sub chunks is (ADDCHUNKNUM HASH)+, where ADDCHUNKNUM
// is a 4 byte chunk number, and HASH is 32 bytes.
MOZ_ASSERT(aChunk.Length() % 36 == 0,
"Chunk length in bytes must be divisible by 36");
uint32_t start = 0;
while (start < aChunk.Length()) {
// Read ADDCHUNKNUM
const nsCSubstring& addChunkStr = Substring(aChunk, start, 4);
start += 4;
uint32_t addChunk;
memcpy(&addChunk, addChunkStr.BeginReading(), 4);
addChunk = PR_ntohl(addChunk);
// Read the hash
Completion hash;
hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
start += COMPLETE_SIZE;
mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries,
const nsACString& aChunk, uint32_t* aStart)
{
NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
"ProcessHostAdd should only be called for prefix hashes.");
if (aNumEntries == 0) {
mTableUpdate->NewAddPrefix(mChunkState.num, aDomain);
return NS_OK;
}
if (*aStart + (PREFIX_SIZE * aNumEntries) > aChunk.Length()) {
NS_WARNING("Chunk is not long enough to contain the expected entries.");
return NS_ERROR_FAILURE;
}
for (uint8_t i = 0; i < aNumEntries; i++) {
Prefix hash;
hash.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
mTableUpdate->NewAddPrefix(mChunkState.num, hash);
*aStart += PREFIX_SIZE;
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries,
const nsACString& aChunk, uint32_t *aStart)
{
NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
"ProcessHostSub should only be called for prefix hashes.");
if (aNumEntries == 0) {
if ((*aStart) + 4 > aChunk.Length()) {
NS_WARNING("Received a zero-entry sub chunk without an associated add.");
return NS_ERROR_FAILURE;
}
const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
*aStart += 4;
uint32_t addChunk;
memcpy(&addChunk, addChunkStr.BeginReading(), 4);
addChunk = PR_ntohl(addChunk);
mTableUpdate->NewSubPrefix(addChunk, aDomain, mChunkState.num);
return NS_OK;
}
if (*aStart + ((PREFIX_SIZE + 4) * aNumEntries) > aChunk.Length()) {
NS_WARNING("Chunk is not long enough to contain the expected entries.");
return NS_ERROR_FAILURE;
}
for (uint8_t i = 0; i < aNumEntries; i++) {
const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
*aStart += 4;
uint32_t addChunk;
memcpy(&addChunk, addChunkStr.BeginReading(), 4);
addChunk = PR_ntohl(addChunk);
Prefix prefix;
prefix.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
*aStart += PREFIX_SIZE;
mTableUpdate->NewSubPrefix(addChunk, prefix, mChunkState.num);
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessHostAddComplete(uint8_t aNumEntries,
const nsACString& aChunk, uint32_t* aStart)
{
NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
"ProcessHostAddComplete should only be called for complete hashes.");
if (aNumEntries == 0) {
// this is totally comprehensible.
// My sarcasm detector is going off!
NS_WARNING("Expected > 0 entries for a 32-byte hash add.");
return NS_OK;
}
if (*aStart + (COMPLETE_SIZE * aNumEntries) > aChunk.Length()) {
NS_WARNING("Chunk is not long enough to contain the expected entries.");
return NS_ERROR_FAILURE;
}
for (uint8_t i = 0; i < aNumEntries; i++) {
Completion hash;
hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
mTableUpdate->NewAddComplete(mChunkState.num, hash);
*aStart += COMPLETE_SIZE;
}
return NS_OK;
}
nsresult
ProtocolParser::ProcessHostSubComplete(uint8_t aNumEntries,
const nsACString& aChunk, uint32_t* aStart)
{
NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
"ProcessHostSubComplete should only be called for complete hashes.");
if (aNumEntries == 0) {
// this is totally comprehensible.
NS_WARNING("Expected > 0 entries for a 32-byte hash sub.");
return NS_OK;
}
if (*aStart + ((COMPLETE_SIZE + 4) * aNumEntries) > aChunk.Length()) {
NS_WARNING("Chunk is not long enough to contain the expected entries.");
return NS_ERROR_FAILURE;
}
for (uint8_t i = 0; i < aNumEntries; i++) {
Completion hash;
hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
*aStart += COMPLETE_SIZE;
const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
*aStart += 4;
uint32_t addChunk;
memcpy(&addChunk, addChunkStr.BeginReading(), 4);
addChunk = PR_ntohl(addChunk);
mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
}
return NS_OK;
}
bool
ProtocolParser::NextLine(nsACString& aLine)
{
int32_t newline = mPending.FindChar('\n');
if (newline == kNotFound) {
return false;
}
aLine.Assign(Substring(mPending, 0, newline));
mPending.Cut(0, newline + 1);
return true;
}
void
ProtocolParser::CleanupUpdates()
{
for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
delete mTableUpdates[i];
}
mTableUpdates.Clear();
}
TableUpdate *
ProtocolParser::GetTableUpdate(const nsACString& aTable)
{
for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
if (aTable.Equals(mTableUpdates[i]->TableName())) {
return mTableUpdates[i];
}
}
// We free automatically on destruction, ownership of these
// updates can be transferred to DBServiceWorker, which passes
// them back to Classifier when doing the updates, and that
// will free them.
TableUpdate *update = new TableUpdate(aTable);
mTableUpdates.AppendElement(update);
return update;
}
} // namespace safebrowsing
} // namespace mozilla
@@ -1,119 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ProtocolParser_h__
#define ProtocolParser_h__
#include "HashStore.h"
#include "nsICryptoHMAC.h"
namespace mozilla {
namespace safebrowsing {
/**
* Some helpers for parsing the safe
*/
class ProtocolParser {
public:
struct ForwardedUpdate {
nsCString table;
nsCString url;
};
ProtocolParser();
~ProtocolParser();
nsresult Status() const { return mUpdateStatus; }
nsresult Init(nsICryptoHash* aHasher);
void SetCurrentTable(const nsACString& aTable);
nsresult Begin();
nsresult AppendStream(const nsACString& aData);
// Forget the table updates that were created by this pass. It
// becomes the caller's responsibility to free them. This is shitty.
TableUpdate *GetTableUpdate(const nsACString& aTable);
void ForgetTableUpdates() { mTableUpdates.Clear(); }
nsTArray<TableUpdate*> &GetTableUpdates() { return mTableUpdates; }
// Update information.
const nsTArray<ForwardedUpdate> &Forwards() const { return mForwards; }
int32_t UpdateWait() { return mUpdateWait; }
bool ResetRequested() { return mResetRequested; }
private:
nsresult ProcessControl(bool* aDone);
nsresult ProcessExpirations(const nsCString& aLine);
nsresult ProcessChunkControl(const nsCString& aLine);
nsresult ProcessForward(const nsCString& aLine);
nsresult AddForward(const nsACString& aUrl);
nsresult ProcessChunk(bool* done);
// Remove this, it's only used for testing
nsresult ProcessPlaintextChunk(const nsACString& aChunk);
nsresult ProcessShaChunk(const nsACString& aChunk);
nsresult ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries,
const nsACString& aChunk, uint32_t* aStart);
nsresult ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries,
const nsACString& aChunk, uint32_t* aStart);
nsresult ProcessHostAddComplete(uint8_t aNumEntries, const nsACString& aChunk,
uint32_t *aStart);
nsresult ProcessHostSubComplete(uint8_t numEntries, const nsACString& aChunk,
uint32_t* start);
// Digest chunks are very similar to shavar chunks, except digest chunks
// always contain the full hash, so there is no need for chunk data to
// contain prefix sizes.
nsresult ProcessDigestChunk(const nsACString& aChunk);
nsresult ProcessDigestAdd(const nsACString& aChunk);
nsresult ProcessDigestSub(const nsACString& aChunk);
bool NextLine(nsACString& aLine);
void CleanupUpdates();
enum ParserState {
PROTOCOL_STATE_CONTROL,
PROTOCOL_STATE_CHUNK
};
ParserState mState;
enum ChunkType {
// Types for shavar tables.
CHUNK_ADD,
CHUNK_SUB,
// Types for digest256 tables. digest256 tables differ in format from
// shavar tables since they only contain complete hashes.
CHUNK_ADD_DIGEST,
CHUNK_SUB_DIGEST
};
struct ChunkState {
ChunkType type;
uint32_t num;
uint32_t hashSize;
uint32_t length;
void Clear() { num = 0; hashSize = 0; length = 0; }
};
ChunkState mChunkState;
nsCOMPtr<nsICryptoHash> mCryptoHash;
nsresult mUpdateStatus;
nsCString mPending;
uint32_t mUpdateWait;
bool mResetRequested;
nsTArray<ForwardedUpdate> mForwards;
// Keep track of updates to apply before passing them to the DBServiceWorkers.
nsTArray<TableUpdate*> mTableUpdates;
// Updates to apply to the current table being parsed.
TableUpdate *mTableUpdate;
};
}
}
#endif
@@ -1,235 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = ["SafeBrowsing"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
// Skip all the ones containining "test", because we never need to ask for
// updates for them.
function getLists(prefName) {
let pref = Services.prefs.getCharPref(prefName);
// Splitting an empty string returns [''], we really want an empty array.
if (!pref) {
return [];
}
return pref.split(",")
.filter(function(value) { return value.indexOf("test-") == -1; })
.map(function(value) { return value.trim(); });
}
// These may be a comma-separated lists of tables.
const phishingLists = getLists("urlclassifier.phishTable");
const malwareLists = getLists("urlclassifier.malwareTable");
const downloadBlockLists = getLists("urlclassifier.downloadBlockTable");
const downloadAllowLists = getLists("urlclassifier.downloadAllowTable");
const trackingProtectionLists = getLists("urlclassifier.trackingTable");
var debug = false;
function log(...stuff) {
if (!debug)
return;
var d = new Date();
let msg = "SafeBrowsing: " + d.toTimeString() + ": " + stuff.join(" ");
Services.console.logStringMessage(msg);
dump(msg + "\n");
}
this.SafeBrowsing = {
init: function() {
if (this.initialized) {
log("Already initialized");
return;
}
Services.prefs.addObserver("browser.safebrowsing", this.readPrefs.bind(this), false);
Services.prefs.addObserver("privacy.trackingprotection", this.readPrefs.bind(this), false);
this.readPrefs();
// Register our two types of tables, and add custom Mozilla entries
let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
getService(Ci.nsIUrlListManager);
for (let i = 0; i < phishingLists.length; ++i) {
listManager.registerTable(phishingLists[i], this.updateURL, this.gethashURL);
}
for (let i = 0; i < malwareLists.length; ++i) {
listManager.registerTable(malwareLists[i], this.updateURL, this.gethashURL);
}
for (let i = 0; i < downloadBlockLists.length; ++i) {
listManager.registerTable(downloadBlockLists[i], this.updateURL, this.gethashURL);
}
for (let i = 0; i < downloadAllowLists.length; ++i) {
listManager.registerTable(downloadAllowLists[i], this.updateURL, this.gethashURL);
}
for (let i = 0; i < trackingProtectionLists.length; ++i) {
listManager.registerTable(trackingProtectionLists[i],
this.trackingUpdateURL,
this.trackingGethashURL);
}
this.addMozEntries();
this.controlUpdateChecking();
this.initialized = true;
log("init() finished");
},
initialized: false,
phishingEnabled: false,
malwareEnabled: false,
updateURL: null,
gethashURL: null,
reportURL: null,
reportGenericURL: null,
reportErrorURL: null,
reportPhishURL: null,
reportMalwareURL: null,
reportMalwareErrorURL: null,
getReportURL: function(kind) {
return this["report" + kind + "URL"];
},
readPrefs: function() {
log("reading prefs");
debug = Services.prefs.getBoolPref("browser.safebrowsing.debug");
this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.enabled");
this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled");
this.updateProviderURLs();
// XXX The listManager backend gets confused if this is called before the
// lists are registered. So only call it here when a pref changes, and not
// when doing initialization. I expect to refactor this later, so pardon the hack.
if (this.initialized) {
this.controlUpdateChecking();
}
},
updateProviderURLs: function() {
try {
var clientID = Services.prefs.getCharPref("browser.safebrowsing.id");
} catch(e) {
var clientID = Services.appinfo.name;
}
log("initializing safe browsing URLs, client id ", clientID);
let basePref = "browser.safebrowsing.";
// Urls to HTML report pages
this.reportURL = Services.urlFormatter.formatURLPref(basePref + "reportURL");
this.reportGenericURL = Services.urlFormatter.formatURLPref(basePref + "reportGenericURL");
this.reportErrorURL = Services.urlFormatter.formatURLPref(basePref + "reportErrorURL");
this.reportPhishURL = Services.urlFormatter.formatURLPref(basePref + "reportPhishURL");
this.reportMalwareURL = Services.urlFormatter.formatURLPref(basePref + "reportMalwareURL");
this.reportMalwareErrorURL = Services.urlFormatter.formatURLPref(basePref + "reportMalwareErrorURL");
// Urls used to update DB
this.updateURL = Services.urlFormatter.formatURLPref(basePref + "updateURL");
this.gethashURL = Services.urlFormatter.formatURLPref(basePref + "gethashURL");
this.updateURL = this.updateURL.replace("SAFEBROWSING_ID", clientID);
this.gethashURL = this.gethashURL.replace("SAFEBROWSING_ID", clientID);
this.trackingUpdateURL = Services.urlFormatter.formatURLPref(
"browser.trackingprotection.updateURL");
this.trackingGethashURL = Services.urlFormatter.formatURLPref(
"browser.trackingprotection.gethashURL");
},
controlUpdateChecking: function() {
log("phishingEnabled:", this.phishingEnabled, "malwareEnabled:",
this.malwareEnabled, "trackingEnabled:", this.trackingEnabled);
let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
getService(Ci.nsIUrlListManager);
for (let i = 0; i < phishingLists.length; ++i) {
if (this.phishingEnabled) {
listManager.enableUpdate(phishingLists[i]);
} else {
listManager.disableUpdate(phishingLists[i]);
}
}
for (let i = 0; i < malwareLists.length; ++i) {
if (this.malwareEnabled) {
listManager.enableUpdate(malwareLists[i]);
} else {
listManager.disableUpdate(malwareLists[i]);
}
}
for (let i = 0; i < downloadBlockLists.length; ++i) {
if (this.malwareEnabled) {
listManager.enableUpdate(downloadBlockLists[i]);
} else {
listManager.disableUpdate(downloadBlockLists[i]);
}
}
for (let i = 0; i < downloadAllowLists.length; ++i) {
if (this.malwareEnabled) {
listManager.enableUpdate(downloadAllowLists[i]);
} else {
listManager.disableUpdate(downloadAllowLists[i]);
}
}
for (let i = 0; i < trackingProtectionLists.length; ++i) {
if (this.trackingEnabled) {
listManager.enableUpdate(trackingProtectionLists[i]);
} else {
listManager.disableUpdate(trackingProtectionLists[i]);
}
}
listManager.maybeToggleUpdateChecking();
},
addMozEntries: function() {
// Add test entries to the DB.
// XXX bug 779008 - this could be done by DB itself?
const phishURL = "itisatrap.org/firefox/its-a-trap.html";
const malwareURL = "itisatrap.org/firefox/its-an-attack.html";
let update = "n:1000\ni:test-malware-simple\nad:1\n" +
"a:1:32:" + malwareURL.length + "\n" +
malwareURL;
update += "n:1000\ni:test-phish-simple\nad:1\n" +
"a:1:32:" + phishURL.length + "\n" +
phishURL;
log("addMozEntries:", update);
let db = Cc["@mozilla.org/url-classifier/dbservice;1"].
getService(Ci.nsIUrlClassifierDBService);
// nsIUrlClassifierUpdateObserver
let dummyListener = {
updateUrlRequested: function() { },
streamFinished: function() { },
updateError: function() { },
updateSuccess: function() { }
};
try {
db.beginUpdate(dummyListener, "test-malware-simple,test-phish-simple", "");
db.beginStream("", "");
db.updateStream(update);
db.finishStream();
db.finishUpdate();
} catch(ex) {
// beginUpdate will throw harmlessly if there's an existing update in progress, ignore failures.
log("addMozEntries failed!", ex);
}
},
};
@@ -1,458 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
// This is the only implementation of nsIUrlListManager.
// A class that manages lists, namely white and black lists for
// phishing or malware protection. The ListManager knows how to fetch,
// update, and store lists.
//
// There is a single listmanager for the whole application.
//
// TODO more comprehensive update tests, for example add unittest check
// that the listmanagers tables are properly written on updates
// Log only if browser.safebrowsing.debug is true
this.log = function log(...stuff) {
var prefs_ = new G_Preferences();
var debug = prefs_.getPref("browser.safebrowsing.debug");
if (!debug) {
return;
}
var d = new Date();
let msg = "listmanager: " + d.toTimeString() + ": " + stuff.join(" ");
Services.console.logStringMessage(msg);
dump(msg + "\n");
}
this.QueryAdapter = function QueryAdapter(callback) {
this.callback_ = callback;
};
QueryAdapter.prototype.handleResponse = function(value) {
this.callback_.handleEvent(value);
}
/**
* A ListManager keeps track of black and white lists and knows
* how to update them.
*
* @constructor
*/
this.PROT_ListManager = function PROT_ListManager() {
log("Initializing list manager");
this.prefs_ = new G_Preferences();
this.updateInterval = this.prefs_.getPref("urlclassifier.updateinterval", 30 * 60) * 1000;
// A map of tableNames to objects of type
// { updateUrl: <updateUrl>, gethashUrl: <gethashUrl> }
this.tablesData = {};
// A map of updateUrls to maps of tables requiring updates, e.g.
// { safebrowsing-update-url: { goog-phish-shavar: true,
// goog-malware-shavar: true }
this.needsUpdate_ = {};
this.observerServiceObserver_ = new G_ObserverServiceObserver(
'quit-application',
BindToObject(this.shutdown_, this),
true /*only once*/);
// A map of updateUrls to single-use G_Alarms. An entry exists if and only if
// there is at least one table with updates enabled for that url. G_Alarms
// are reset when enabling/disabling updates or on update callbacks (update
// success, update failure, download error).
this.updateCheckers_ = {};
this.requestBackoffs_ = {};
this.dbService_ = Cc["@mozilla.org/url-classifier/dbservice;1"]
.getService(Ci.nsIUrlClassifierDBService);
this.hashCompleter_ = Cc["@mozilla.org/url-classifier/hashcompleter;1"]
.getService(Ci.nsIUrlClassifierHashCompleter);
}
/**
* xpcom-shutdown callback
* Delete all of our data tables which seem to leak otherwise.
*/
PROT_ListManager.prototype.shutdown_ = function() {
for (var name in this.tablesData) {
delete this.tablesData[name];
}
}
/**
* Register a new table table
* @param tableName - the name of the table
* @param updateUrl - the url for updating the table
* @param gethashUrl - the url for fetching hash completions
* @returns true if the table could be created; false otherwise
*/
PROT_ListManager.prototype.registerTable = function(tableName,
updateUrl,
gethashUrl) {
log("registering " + tableName + " with " + updateUrl);
if (!updateUrl) {
log("Can't register table " + tableName + " without updateUrl");
return false;
}
this.tablesData[tableName] = {};
this.tablesData[tableName].updateUrl = updateUrl;
this.tablesData[tableName].gethashUrl = gethashUrl;
// Keep track of all of our update URLs.
if (!this.needsUpdate_[updateUrl]) {
this.needsUpdate_[updateUrl] = {};
/* Backoff interval should be between 30 and 60 minutes. */
var backoffInterval = 30 * 60 * 1000;
backoffInterval += Math.floor(Math.random() * (30 * 60 * 1000));
log("Creating request backoff for " + updateUrl);
this.requestBackoffs_[updateUrl] = new RequestBackoff(2 /* max errors */,
60*1000 /* retry interval, 1 min */,
4 /* num requests */,
60*60*1000 /* request time, 60 min */,
backoffInterval /* backoff interval, 60 min */,
8*60*60*1000 /* max backoff, 8hr */);
}
this.needsUpdate_[updateUrl][tableName] = false;
return true;
}
PROT_ListManager.prototype.getGethashUrl = function(tableName) {
if (this.tablesData[tableName] && this.tablesData[tableName].gethashUrl) {
return this.tablesData[tableName].gethashUrl;
}
return "";
}
/**
* Enable updates for some tables
* @param tables - an array of table names that need updating
*/
PROT_ListManager.prototype.enableUpdate = function(tableName) {
var table = this.tablesData[tableName];
if (table) {
log("Enabling table updates for " + tableName);
this.needsUpdate_[table.updateUrl][tableName] = true;
}
}
/**
* Returns true if any table associated with the updateUrl requires updates.
* @param updateUrl - the updateUrl
*/
PROT_ListManager.prototype.updatesNeeded_ = function(updateUrl) {
let updatesNeeded = false;
for (var tableName in this.needsUpdate_[updateUrl]) {
if (this.needsUpdate_[updateUrl][tableName]) {
updatesNeeded = true;
}
}
return updatesNeeded;
}
/**
* Disables updates for some tables
* @param tables - an array of table names that no longer need updating
*/
PROT_ListManager.prototype.disableUpdate = function(tableName) {
var table = this.tablesData[tableName];
if (table) {
log("Disabling table updates for " + tableName);
this.needsUpdate_[table.updateUrl][tableName] = false;
if (!this.updatesNeeded_(table.updateUrl) &&
this.updateCheckers_[table.updateUrl]) {
this.updateCheckers_[table.updateUrl].cancel();
this.updateCheckers_[table.updateUrl] = null;
}
}
}
/**
* Determine if we have some tables that need updating.
*/
PROT_ListManager.prototype.requireTableUpdates = function() {
for (var name in this.tablesData) {
// Tables that need updating even if other tables don't require it
if (this.needsUpdate_[this.tablesData[name].updateUrl][name]) {
return true;
}
}
return false;
}
/**
* Acts as a nsIUrlClassifierCallback for getTables.
*/
PROT_ListManager.prototype.kickoffUpdate_ = function (onDiskTableData)
{
this.startingUpdate_ = false;
var initialUpdateDelay = 3000;
// If the user has never downloaded tables, do the check now.
log("needsUpdate: " + JSON.stringify(this.needsUpdate_, undefined, 2));
for (var updateUrl in this.needsUpdate_) {
// If we haven't already kicked off updates for this updateUrl, set a
// non-repeating timer for it. The timer delay will be reset either on
// updateSuccess to this.updateinterval, or backed off on downloadError.
// Don't set the updateChecker unless at least one table has updates
// enabled.
if (this.updatesNeeded_(updateUrl) && !this.updateCheckers_[updateUrl]) {
log("Initializing update checker for " + updateUrl);
this.updateCheckers_[updateUrl] =
new G_Alarm(BindToObject(this.checkForUpdates, this, updateUrl),
initialUpdateDelay, false /* repeating */);
} else {
log("No updates needed or already initialized for " + updateUrl);
}
}
}
PROT_ListManager.prototype.stopUpdateCheckers = function() {
log("Stopping updates");
for (var updateUrl in this.updateCheckers_) {
this.updateCheckers_[updateUrl].cancel();
this.updateCheckers_[updateUrl] = null;
}
}
/**
* Determine if we have any tables that require updating. Different
* Wardens may call us with new tables that need to be updated.
*/
PROT_ListManager.prototype.maybeToggleUpdateChecking = function() {
// We update tables if we have some tables that want updates. If there
// are no tables that want to be updated - we dont need to check anything.
if (this.requireTableUpdates()) {
log("Starting managing lists");
// Get the list of existing tables from the DBService before making any
// update requests.
if (!this.startingUpdate_) {
this.startingUpdate_ = true;
// check the current state of tables in the database
this.dbService_.getTables(BindToObject(this.kickoffUpdate_, this));
}
} else {
log("Stopping managing lists (if currently active)");
this.stopUpdateCheckers(); // Cancel pending updates
}
}
/**
* Provides an exception free way to look up the data in a table. We
* use this because at certain points our tables might not be loaded,
* and querying them could throw.
*
* @param table String Name of the table that we want to consult
* @param key Principal being used to lookup the database
* @param callback nsIUrlListManagerCallback (ie., Function) given false or the
* value in the table corresponding to key. If the table name does not
* exist, we return false, too.
*/
PROT_ListManager.prototype.safeLookup = function(key, callback) {
try {
log("safeLookup: " + key);
var cb = new QueryAdapter(callback);
this.dbService_.lookup(key,
BindToObject(cb.handleResponse, cb),
true);
} catch(e) {
log("safeLookup masked failure for key " + key + ": " + e);
callback.handleEvent("");
}
}
/**
* Updates our internal tables from the update server
*
* @param updateUrl: request updates for tables associated with that url, or
* for all tables if the url is empty.
*/
PROT_ListManager.prototype.checkForUpdates = function(updateUrl) {
log("checkForUpdates with " + updateUrl);
// See if we've triggered the request backoff logic.
if (!updateUrl) {
return false;
}
if (!this.requestBackoffs_[updateUrl] ||
!this.requestBackoffs_[updateUrl].canMakeRequest()) {
log("Can't make update request");
return false;
}
// Grab the current state of the tables from the database
this.dbService_.getTables(BindToObject(this.makeUpdateRequest_, this,
updateUrl));
return true;
}
/**
* Method that fires the actual HTTP update request.
* First we reset any tables that have disappeared.
* @param tableData List of table data already in the database, in the form
* tablename;<chunk ranges>\n
*/
PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) {
log("this.tablesData: " + JSON.stringify(this.tablesData, undefined, 2));
log("existing chunks: " + tableData + "\n");
// Disallow blank updateUrls
if (!updateUrl) {
return;
}
// An object of the form
// { tableList: comma-separated list of tables to request,
// tableNames: map of tables that need updating,
// request: list of tables and existing chunk ranges from tableData
// }
var streamerMap = { tableList: null, tableNames: {}, request: "" };
for (var tableName in this.tablesData) {
// Skip tables not matching this update url
if (this.tablesData[tableName].updateUrl != updateUrl) {
continue;
}
if (this.needsUpdate_[this.tablesData[tableName].updateUrl][tableName]) {
streamerMap.tableNames[tableName] = true;
}
if (!streamerMap.tableList) {
streamerMap.tableList = tableName;
} else {
streamerMap.tableList += "," + tableName;
}
}
// Build the request. For each table already in the database, include the
// chunk data from the database
var lines = tableData.split("\n");
for (var i = 0; i < lines.length; i++) {
var fields = lines[i].split(";");
var name = fields[0];
if (streamerMap.tableNames[name]) {
streamerMap.request += lines[i] + "\n";
delete streamerMap.tableNames[name];
}
}
// For each requested table that didn't have chunk data in the database,
// request it fresh
for (let tableName in streamerMap.tableNames) {
streamerMap.request += tableName + ";\n";
}
log("update request: " + JSON.stringify(streamerMap, undefined, 2) + "\n");
// Don't send an empty request.
if (streamerMap.request.length > 0) {
this.makeUpdateRequestForEntry_(updateUrl, streamerMap.tableList,
streamerMap.request);
} else {
// We were disabled between kicking off getTables and now.
log("Not sending empty request");
}
}
PROT_ListManager.prototype.makeUpdateRequestForEntry_ = function(updateUrl,
tableList,
request) {
log("makeUpdateRequestForEntry_: request " + request +
" update: " + updateUrl + " tablelist: " + tableList + "\n");
var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"]
.getService(Ci.nsIUrlClassifierStreamUpdater);
this.requestBackoffs_[updateUrl].noteRequest();
if (!streamer.downloadUpdates(
tableList,
request,
updateUrl,
BindToObject(this.updateSuccess_, this, tableList, updateUrl),
BindToObject(this.updateError_, this, tableList, updateUrl),
BindToObject(this.downloadError_, this, tableList, updateUrl))) {
// Our alarm gets reset in one of the 3 callbacks.
log("pending update, queued request until later");
}
}
/**
* Callback function if the update request succeeded.
* @param waitForUpdate String The number of seconds that the client should
* wait before requesting again.
*/
PROT_ListManager.prototype.updateSuccess_ = function(tableList, updateUrl,
waitForUpdate) {
log("update success for " + tableList + " from " + updateUrl + ": " +
waitForUpdate + "\n");
var delay;
if (waitForUpdate) {
delay = parseInt(waitForUpdate, 10);
}
// As long as the delay is something sane (5 minutes or more), update
// our delay time for requesting updates. We always use a non-repeating
// timer since the delay is set differently at every callback.
if (delay >= (5 * 60)) {
log("Waiting " + delay + " seconds");
delay = delay * 1000;
} else {
log("Ignoring delay from server, waiting " + this.updateInterval / 1000);
delay = this.updateInterval;
}
this.updateCheckers_[updateUrl] =
new G_Alarm(BindToObject(this.checkForUpdates, this, updateUrl),
delay, false);
// Let the backoff object know that we completed successfully.
this.requestBackoffs_[updateUrl].noteServerResponse(200);
}
/**
* Callback function if the update request succeeded.
* @param result String The error code of the failure
*/
PROT_ListManager.prototype.updateError_ = function(table, updateUrl, result) {
log("update error for " + table + " from " + updateUrl + ": " + result + "\n");
// There was some trouble applying the updates. Don't try again for at least
// updateInterval seconds.
this.updateCheckers_[updateUrl] =
new G_Alarm(BindToObject(this.checkForUpdates, this, updateUrl),
this.updateInterval, false);
}
/**
* Callback function when the download failed
* @param status String http status or an empty string if connection refused.
*/
PROT_ListManager.prototype.downloadError_ = function(table, updateUrl, status) {
log("download error for " + table + ": " + status + "\n");
// If status is empty, then we assume that we got an NS_CONNECTION_REFUSED
// error. In this case, we treat this is a http 500 error.
if (!status) {
status = 500;
}
status = parseInt(status, 10);
this.requestBackoffs_[updateUrl].noteServerResponse(status);
var delay = this.updateInterval;
if (this.requestBackoffs_[updateUrl].isErrorStatus(status)) {
// Schedule an update for when our backoff is complete
delay = this.requestBackoffs_[updateUrl].nextRequestDelay();
} else {
log("Got non error status for error callback?!");
}
this.updateCheckers_[updateUrl] =
new G_Alarm(BindToObject(this.checkForUpdates, this, updateUrl),
delay, false);
}
PROT_ListManager.prototype.QueryInterface = function(iid) {
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIUrlListManager) ||
iid.equals(Ci.nsITimerCallback))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
@@ -1,157 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// An Alarm fires a callback after a certain amount of time, or at
// regular intervals. It's a convenient replacement for
// setTimeout/Interval when you don't want to bind to a specific
// window.
//
// The ConditionalAlarm is an Alarm that cancels itself if its callback
// returns a value that type-converts to true.
//
// Example:
//
// function foo() { dump('hi'); };
// new G_Alarm(foo, 10*1000); // Fire foo in 10 seconds
// new G_Alarm(foo, 10*1000, true /*repeat*/); // Fire foo every 10 seconds
// new G_Alarm(foo, 10*1000, true, 7); // Fire foo every 10 seconds
// // seven times
// new G_ConditionalAlarm(foo, 1000, true); // Fire every sec until foo()==true
//
// // Fire foo every 10 seconds until foo returns true or until it fires seven
// // times, whichever happens first.
// new G_ConditionalAlarm(foo, 10*1000, true /*repeating*/, 7);
//
// TODO: maybe pass an isFinal flag to the callback if they opted to
// set maxTimes and this is the last iteration?
/**
* Set an alarm to fire after a given amount of time, or at specific
* intervals.
*
* @param callback Function to call when the alarm fires
* @param delayMS Number indicating the length of the alarm period in ms
* @param opt_repeating Boolean indicating whether this should fire
* periodically
* @param opt_maxTimes Number indicating a maximum number of times to
* repeat (obviously only useful when opt_repeating==true)
*/
this.G_Alarm =
function G_Alarm(callback, delayMS, opt_repeating, opt_maxTimes) {
this.debugZone = "alarm";
this.callback_ = callback;
this.repeating_ = !!opt_repeating;
this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
var type = opt_repeating ?
this.timer_.TYPE_REPEATING_SLACK :
this.timer_.TYPE_ONE_SHOT;
this.maxTimes_ = opt_maxTimes ? opt_maxTimes : null;
this.nTimes_ = 0;
this.observerServiceObserver_ = new G_ObserverServiceObserver(
'xpcom-shutdown',
BindToObject(this.cancel, this));
// Ask the timer to use nsITimerCallback (.notify()) when ready
this.timer_.initWithCallback(this, delayMS, type);
}
/**
* Cancel this timer
*/
G_Alarm.prototype.cancel = function() {
if (!this.timer_) {
return;
}
this.timer_.cancel();
// Break circular reference created between this.timer_ and the G_Alarm
// instance (this)
this.timer_ = null;
this.callback_ = null;
// We don't need the shutdown observer anymore
this.observerServiceObserver_.unregister();
}
/**
* Invoked by the timer when it fires
*
* @param timer Reference to the nsITimer which fired (not currently
* passed along)
*/
G_Alarm.prototype.notify = function(timer) {
// fire callback and save results
var ret = this.callback_();
// If they've given us a max number of times to fire, enforce it
this.nTimes_++;
if (this.repeating_ &&
typeof this.maxTimes_ == "number"
&& this.nTimes_ >= this.maxTimes_) {
this.cancel();
} else if (!this.repeating_) {
// Clear out the callback closure for TYPE_ONE_SHOT timers
this.cancel();
}
// We don't cancel/cleanup timers that repeat forever until either
// xpcom-shutdown occurs or cancel() is called explicitly.
return ret;
}
G_Alarm.prototype.setDelay = function(delay) {
this.timer_.delay = delay;
}
/**
* XPCOM cruft
*/
G_Alarm.prototype.QueryInterface = function(iid) {
if (iid.equals(Components.interfaces.nsISupports) ||
iid.equals(Components.interfaces.nsITimerCallback))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
/**
* An alarm with the additional property that it cancels itself if its
* callback returns true.
*
* For parameter documentation, see G_Alarm
*/
this.G_ConditionalAlarm =
function G_ConditionalAlarm(callback, delayMS, opt_repeating, opt_maxTimes) {
G_Alarm.call(this, callback, delayMS, opt_repeating, opt_maxTimes);
this.debugZone = "conditionalalarm";
}
G_ConditionalAlarm.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
}
G_ConditionalAlarm.inherits(G_Alarm);
/**
* Invoked by the timer when it fires
*
* @param timer Reference to the nsITimer which fired (not currently
* passed along)
*/
G_ConditionalAlarm.prototype.notify = function(timer) {
// Call G_Alarm::notify
var rv = G_Alarm.prototype.notify.call(this, timer);
if (this.repeating_ && rv) {
G_Debug(this, "Callback of a repeating alarm returned true; cancelling.");
this.cancel();
}
}
@@ -1,176 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// A very thin wrapper around nsICryptoHash. It's not strictly
// necessary, but makes the code a bit cleaner and gives us the
// opportunity to verify that our implementations give the results that
// we expect, for example if we have to interoperate with a server.
//
// The digest* methods reset the state of the hasher, so it's
// necessary to call init() explicitly after them.
//
// Works only in Firefox 1.5+.
//
// IMPORTANT NOTE: Due to https://bugzilla.mozilla.org/show_bug.cgi?id=321024
// you cannot use the cryptohasher before app-startup. The symptom of doing
// so is a segfault in NSS.
/**
* Instantiate a new hasher. You must explicitly call init() before use!
*/
this.G_CryptoHasher =
function G_CryptoHasher() {
this.debugZone = "cryptohasher";
this.hasher_ = null;
}
G_CryptoHasher.algorithms = {
MD2: Ci.nsICryptoHash.MD2,
MD5: Ci.nsICryptoHash.MD5,
SHA1: Ci.nsICryptoHash.SHA1,
SHA256: Ci.nsICryptoHash.SHA256,
SHA384: Ci.nsICryptoHash.SHA384,
SHA512: Ci.nsICryptoHash.SHA512,
};
/**
* Initialize the hasher. This function must be called after every call
* to one of the digest* methods.
*
* @param algorithm Constant from G_CryptoHasher.algorithms specifying the
* algorithm this hasher will use
*/
G_CryptoHasher.prototype.init = function(algorithm) {
var validAlgorithm = false;
for (var alg in G_CryptoHasher.algorithms)
if (algorithm == G_CryptoHasher.algorithms[alg])
validAlgorithm = true;
if (!validAlgorithm)
throw new Error("Invalid algorithm: " + algorithm);
this.hasher_ = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
this.hasher_.init(algorithm);
}
/**
* Update the hash's internal state with input given in a string. Can be
* called multiple times for incrementeal hash updates.
*
* @param input String containing data to hash.
*/
G_CryptoHasher.prototype.updateFromString = function(input) {
if (!this.hasher_)
throw new Error("You must initialize the hasher first!");
var stream = Cc['@mozilla.org/io/string-input-stream;1']
.createInstance(Ci.nsIStringInputStream);
stream.setData(input, input.length);
this.updateFromStream(stream);
}
/**
* Update the hash's internal state with input given in an array. Can be
* called multiple times for incremental hash updates.
*
* @param input Array containing data to hash.
*/
G_CryptoHasher.prototype.updateFromArray = function(input) {
if (!this.hasher_)
throw new Error("You must initialize the hasher first!");
this.hasher_.update(input, input.length);
}
/**
* Update the hash's internal state with input given in a stream. Can be
* called multiple times from incremental hash updates.
*/
G_CryptoHasher.prototype.updateFromStream = function(stream) {
if (!this.hasher_)
throw new Error("You must initialize the hasher first!");
if (stream.available())
this.hasher_.updateFromStream(stream, stream.available());
}
/**
* @returns The hash value as a string (sequence of 8-bit values)
*/
G_CryptoHasher.prototype.digestRaw = function() {
var digest = this.hasher_.finish(false /* not b64 encoded */);
this.hasher_ = null;
return digest;
}
/**
* @returns The hash value as a base64-encoded string
*/
G_CryptoHasher.prototype.digestBase64 = function() {
var digest = this.hasher_.finish(true /* b64 encoded */);
this.hasher_ = null;
return digest;
}
/**
* @returns The hash value as a hex-encoded string
*/
G_CryptoHasher.prototype.digestHex = function() {
var raw = this.digestRaw();
return this.toHex_(raw);
}
/**
* Converts a sequence of values to a hex-encoded string. The input is a
* a string, so you can stick 16-bit values in each character.
*
* @param str String to conver to hex. (Often this is just a sequence of
* 16-bit values)
*
* @returns String containing the hex representation of the input
*/
G_CryptoHasher.prototype.toHex_ = function(str) {
var hexchars = '0123456789ABCDEF';
var hexrep = new Array(str.length * 2);
for (var i = 0; i < str.length; ++i) {
hexrep[i * 2] = hexchars.charAt((str.charCodeAt(i) >> 4) & 15);
hexrep[i * 2 + 1] = hexchars.charAt(str.charCodeAt(i) & 15);
}
return hexrep.join('');
}
#ifdef DEBUG
/**
* Lame unittest function
*/
this.TEST_G_CryptoHasher = function TEST_G_CryptoHasher() {
if (G_GDEBUG) {
var z = "cryptohasher UNITTEST";
G_debugService.enableZone(z);
G_Debug(z, "Starting");
var md5 = function(str) {
var hasher = new G_CryptoHasher();
hasher.init(G_CryptoHasher.algorithms.MD5);
hasher.updateFromString(str);
return hasher.digestHex().toLowerCase();
};
// test vectors from: http://www.faqs.org/rfcs/rfc1321.html
var vectors = {"": "d41d8cd98f00b204e9800998ecf8427e",
"a": "0cc175b9c0f1b6a831c399e269772661",
"abc": "900150983cd24fb0d6963f7d28e17f72",
"message digest": "f96b697d7cb7938d525a2f31aaf161d0",
"abcdefghijklmnopqrstuvwxyz": "c3fcd3d76192e4007dfb496cca67e13b",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789": "d174ab98d277d9f5a5611c2c9f419d9f",
"12345678901234567890123456789012345678901234567890123456789012345678901234567890": "57edf4a22be3c955ac49da2e2107b67a"};
G_Debug(z, "PASSED");
}
}
#endif
@@ -1,843 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifdef DEBUG
// Generic logging/debugging functionality that:
//
// (*) when disabled compiles to no-ops at worst (for calls to the service)
// and to nothing at best (calls to G_Debug() and similar are compiled
// away when you use a jscompiler that strips dead code)
//
// (*) has dynamically configurable/creatable debugging "zones" enabling
// selective logging
//
// (*) hides its plumbing so that all calls in different zones are uniform,
// so you can drop files using this library into other apps that use it
// without any configuration
//
// (*) can be controlled programmatically or via preferences. The
// preferences that control the service and its zones are under
// the preference branch "safebrowsing-debug-service."
//
// (*) outputs function call traces when the "loggifier" zone is enabled
//
// (*) can write output to logfiles so that you can get a call trace
// from someone who is having a problem
//
// Example:
//
// var G_GDEBUG = true // Enable this module
// var G_debugService = new G_DebugService(); // in global context
//
// // You can use it with arbitrary primitive first arguement
// G_Debug("myzone", "Yo yo yo"); // outputs: [myzone] Yo yo yo\n
//
// // But it's nice to use it with an object; it will probe for the zone name
// function Obj() {
// this.debugZone = "someobj";
// }
// Obj.prototype.foo = function() {
// G_Debug(this, "foo called");
// }
// (new Obj).foo(); // outputs: [someobj] foo called\n
//
// G_debugService.loggifier.loggify(Obj.prototype); // enable call tracing
//
// // En/disable specific zones programmatically (you can also use preferences)
// G_debugService.enableZone("somezone");
// G_debugService.disableZone("someotherzone");
// G_debugService.enableAllZones();
//
// // We also have asserts and errors:
// G_Error(this, "Some error occurred"); // will throw
// G_Assert(this, (x > 3), "x not greater than three!"); // will throw
//
// See classes below for more methods.
//
// TODO add code to set prefs when not found to the default value of a tristate
// TODO add error level support
// TODO add ability to turn off console output
//
// -------> TO START DEBUGGING: set G_GDEBUG to true
// These are the functions code will typically call. Everything is
// wrapped in if's so we can compile it away when G_GDEBUG is false.
if (typeof G_GDEBUG == "undefined") {
throw new Error("G_GDEBUG constant must be set before loading debug.js");
}
/**
* Write out a debugging message.
*
* @param who The thingy to convert into a zone name corresponding to the
* zone to which this message belongs
* @param msg Message to output
*/
this.G_Debug = function G_Debug(who, msg) {
if (G_GDEBUG) {
G_GetDebugZone(who).debug(msg);
}
}
/**
* Debugs loudly
*/
this.G_DebugL = function G_DebugL(who, msg) {
if (G_GDEBUG) {
var zone = G_GetDebugZone(who);
if (zone.zoneIsEnabled()) {
G_debugService.dump(
"\n************************************************************\n");
G_Debug(who, msg);
G_debugService.dump(
"************************************************************\n\n");
}
}
}
/**
* Write out a call tracing message
*
* @param who The thingy to convert into a zone name corresponding to the
* zone to which this message belongs
* @param msg Message to output
*/
this.G_TraceCall = function G_TraceCall(who, msg) {
if (G_GDEBUG) {
if (G_debugService.callTracingEnabled()) {
G_debugService.dump(msg + "\n");
}
}
}
/**
* Write out an error (and throw)
*
* @param who The thingy to convert into a zone name corresponding to the
* zone to which this message belongs
* @param msg Message to output
*/
this.G_Error = function G_Error(who, msg) {
if (G_GDEBUG) {
G_GetDebugZone(who).error(msg);
}
}
/**
* Assert something as true and signal an error if it's not
*
* @param who The thingy to convert into a zone name corresponding to the
* zone to which this message belongs
* @param condition Boolean condition to test
* @param msg Message to output
*/
this.G_Assert = function G_Assert(who, condition, msg) {
if (G_GDEBUG) {
G_GetDebugZone(who).assert(condition, msg);
}
}
/**
* Helper function that takes input and returns the DebugZone
* corresponding to it.
*
* @param who Arbitrary input that will be converted into a zone name. Most
* likely an object that has .debugZone property, or a string.
* @returns The DebugZone object corresponding to the input
*/
this.G_GetDebugZone = function G_GetDebugZone(who) {
if (G_GDEBUG) {
var zone = "?";
if (who && who.debugZone) {
zone = who.debugZone;
} else if (typeof who == "string") {
zone = who;
}
return G_debugService.getZone(zone);
}
}
// Classes that implement the functionality.
/**
* A debug "zone" is a string derived from arbitrary types (but
* typically derived from another string or an object). All debugging
* messages using a particular zone can be enabled or disabled
* independent of other zones. This enables you to turn on/off logging
* of particular objects or modules. This object implements a single
* zone and the methods required to use it.
*
* @constructor
* @param service Reference to the DebugService object we use for
* registration
* @param prefix String indicating the unique prefix we should use
* when creating preferences to control this zone
* @param zone String indicating the name of the zone
*/
this.G_DebugZone = function G_DebugZone(service, prefix, zone) {
if (G_GDEBUG) {
this.debugService_ = service;
this.prefix_ = prefix;
this.zone_ = zone;
this.zoneEnabledPrefName_ = prefix + ".zone." + this.zone_;
this.settings_ = new G_DebugSettings();
}
}
/**
* @returns Boolean indicating if this zone is enabled
*/
G_DebugZone.prototype.zoneIsEnabled = function() {
if (G_GDEBUG) {
var explicit = this.settings_.getSetting(this.zoneEnabledPrefName_, null);
if (explicit !== null) {
return explicit;
} else {
return this.debugService_.allZonesEnabled();
}
}
}
/**
* Enable this logging zone
*/
G_DebugZone.prototype.enableZone = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.zoneEnabledPrefName_, true);
}
}
/**
* Disable this logging zone
*/
G_DebugZone.prototype.disableZone = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.zoneEnabledPrefName_, false);
}
}
/**
* Write a debugging message to this zone
*
* @param msg String of message to write
*/
G_DebugZone.prototype.debug = function(msg) {
if (G_GDEBUG) {
if (this.zoneIsEnabled()) {
this.debugService_.dump("[" + this.zone_ + "] " + msg + "\n");
}
}
}
/**
* Write an error to this zone and throw
*
* @param msg String of error to write
*/
G_DebugZone.prototype.error = function(msg) {
if (G_GDEBUG) {
this.debugService_.dump("[" + this.zone_ + "] " + msg + "\n");
throw new Error(msg);
debugger;
}
}
/**
* Assert something as true and error if it is not
*
* @param condition Boolean condition to test
* @param msg String of message to write if is false
*/
G_DebugZone.prototype.assert = function(condition, msg) {
if (G_GDEBUG) {
if (condition !== true) {
G_Error(this.zone_, "ASSERT FAILED: " + msg);
}
}
}
/**
* The debug service handles auto-registration of zones, namespacing
* the zones preferences, and various global settings such as whether
* all zones are enabled.
*
* @constructor
* @param opt_prefix Optional string indicating the unique prefix we should
* use when creating preferences
*/
this.G_DebugService = function G_DebugService(opt_prefix) {
if (G_GDEBUG) {
this.prefix_ = opt_prefix ? opt_prefix : "safebrowsing-debug-service";
this.consoleEnabledPrefName_ = this.prefix_ + ".alsologtoconsole";
this.allZonesEnabledPrefName_ = this.prefix_ + ".enableallzones";
this.callTracingEnabledPrefName_ = this.prefix_ + ".trace-function-calls";
this.logFileEnabledPrefName_ = this.prefix_ + ".logfileenabled";
this.logFileErrorLevelPrefName_ = this.prefix_ + ".logfile-errorlevel";
this.zones_ = {};
this.loggifier = new G_Loggifier();
this.settings_ = new G_DebugSettings();
}
}
// Error levels for reporting console messages to the log.
G_DebugService.ERROR_LEVEL_INFO = "INFO";
G_DebugService.ERROR_LEVEL_WARNING = "WARNING";
G_DebugService.ERROR_LEVEL_EXCEPTION = "EXCEPTION";
/**
* @returns Boolean indicating if we should send messages to the jsconsole
*/
G_DebugService.prototype.alsoDumpToConsole = function() {
if (G_GDEBUG) {
return this.settings_.getSetting(this.consoleEnabledPrefName_, false);
}
}
/**
* @returns whether to log output to a file as well as the console.
*/
G_DebugService.prototype.logFileIsEnabled = function() {
if (G_GDEBUG) {
return this.settings_.getSetting(this.logFileEnabledPrefName_, false);
}
}
/**
* Turns on file logging. dump() output will also go to the file specified by
* setLogFile()
*/
G_DebugService.prototype.enableLogFile = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.logFileEnabledPrefName_, true);
}
}
/**
* Turns off file logging
*/
G_DebugService.prototype.disableLogFile = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.logFileEnabledPrefName_, false);
}
}
/**
* @returns an nsIFile instance pointing to the current log file location
*/
G_DebugService.prototype.getLogFile = function() {
if (G_GDEBUG) {
return this.logFile_;
}
}
/**
* Sets a new log file location
*/
G_DebugService.prototype.setLogFile = function(file) {
if (G_GDEBUG) {
this.logFile_ = file;
}
}
/**
* Enables sending messages to the jsconsole
*/
G_DebugService.prototype.enableDumpToConsole = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.consoleEnabledPrefName_, true);
}
}
/**
* Disables sending messages to the jsconsole
*/
G_DebugService.prototype.disableDumpToConsole = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.consoleEnabledPrefName_, false);
}
}
/**
* @param zone Name of the zone to get
* @returns The DebugZone object corresopnding to input. If not such
* zone exists, a new one is created and returned
*/
G_DebugService.prototype.getZone = function(zone) {
if (G_GDEBUG) {
if (!this.zones_[zone])
this.zones_[zone] = new G_DebugZone(this, this.prefix_, zone);
return this.zones_[zone];
}
}
/**
* @param zone Zone to enable debugging for
*/
G_DebugService.prototype.enableZone = function(zone) {
if (G_GDEBUG) {
var toEnable = this.getZone(zone);
toEnable.enableZone();
}
}
/**
* @param zone Zone to disable debugging for
*/
G_DebugService.prototype.disableZone = function(zone) {
if (G_GDEBUG) {
var toDisable = this.getZone(zone);
toDisable.disableZone();
}
}
/**
* @returns Boolean indicating whether debugging is enabled for all zones
*/
G_DebugService.prototype.allZonesEnabled = function() {
if (G_GDEBUG) {
return this.settings_.getSetting(this.allZonesEnabledPrefName_, false);
}
}
/**
* Enables all debugging zones
*/
G_DebugService.prototype.enableAllZones = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.allZonesEnabledPrefName_, true);
}
}
/**
* Disables all debugging zones
*/
G_DebugService.prototype.disableAllZones = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.allZonesEnabledPrefName_, false);
}
}
/**
* @returns Boolean indicating whether call tracing is enabled
*/
G_DebugService.prototype.callTracingEnabled = function() {
if (G_GDEBUG) {
return this.settings_.getSetting(this.callTracingEnabledPrefName_, false);
}
}
/**
* Enables call tracing
*/
G_DebugService.prototype.enableCallTracing = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.callTracingEnabledPrefName_, true);
}
}
/**
* Disables call tracing
*/
G_DebugService.prototype.disableCallTracing = function() {
if (G_GDEBUG) {
this.settings_.setDefault(this.callTracingEnabledPrefName_, false);
}
}
/**
* Gets the minimum error that will be reported to the log.
*/
G_DebugService.prototype.getLogFileErrorLevel = function() {
if (G_GDEBUG) {
var level = this.settings_.getSetting(this.logFileErrorLevelPrefName_,
G_DebugService.ERROR_LEVEL_EXCEPTION);
return level.toUpperCase();
}
}
/**
* Sets the minimum error level that will be reported to the log.
*/
G_DebugService.prototype.setLogFileErrorLevel = function(level) {
if (G_GDEBUG) {
// normalize case just to make it slightly easier to not screw up.
level = level.toUpperCase();
if (level != G_DebugService.ERROR_LEVEL_INFO &&
level != G_DebugService.ERROR_LEVEL_WARNING &&
level != G_DebugService.ERROR_LEVEL_EXCEPTION) {
throw new Error("Invalid error level specified: {" + level + "}");
}
this.settings_.setDefault(this.logFileErrorLevelPrefName_, level);
}
}
/**
* Internal dump() method
*
* @param msg String of message to dump
*/
G_DebugService.prototype.dump = function(msg) {
if (G_GDEBUG) {
dump(msg);
if (this.alsoDumpToConsole()) {
try {
var console = Components.classes['@mozilla.org/consoleservice;1']
.getService(Components.interfaces.nsIConsoleService);
console.logStringMessage(msg);
} catch(e) {
dump("G_DebugZone ERROR: COULD NOT DUMP TO CONSOLE\n");
}
}
this.maybeDumpToFile(msg);
}
}
/**
* Writes the specified message to the log file, if file logging is enabled.
*/
G_DebugService.prototype.maybeDumpToFile = function(msg) {
if (this.logFileIsEnabled() && this.logFile_) {
/* try to get the correct line end character for this platform */
if (!this._LINE_END_CHAR)
this._LINE_END_CHAR =
Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
.OS == "WINNT" ? "\r\n" : "\n";
if (this._LINE_END_CHAR != "\n")
msg = msg.replace(/\n/g, this._LINE_END_CHAR);
try {
var stream = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
stream.init(this.logFile_,
0x02 | 0x08 | 0x10 /* PR_WRONLY | PR_CREATE_FILE | PR_APPEND */
-1 /* default perms */, 0 /* no special behavior */);
stream.write(msg, msg.length);
} finally {
stream.close();
}
}
}
/**
* Implements nsIConsoleListener.observe(). Gets called when an error message
* gets reported to the console and sends it to the log file as well.
*/
G_DebugService.prototype.observe = function(consoleMessage) {
if (G_GDEBUG) {
var errorLevel = this.getLogFileErrorLevel();
// consoleMessage can be either nsIScriptError or nsIConsoleMessage. The
// latter does not have things like line number, etc. So we special case
// it first.
if (!(consoleMessage instanceof Ci.nsIScriptError)) {
// Only report these messages if the error level is INFO.
if (errorLevel == G_DebugService.ERROR_LEVEL_INFO) {
this.maybeDumpToFile(G_DebugService.ERROR_LEVEL_INFO + ": " +
consoleMessage.message + "\n");
}
return;
}
// We make a local copy of these fields because writing to it doesn't seem
// to work.
var flags = consoleMessage.flags;
var sourceName = consoleMessage.sourceName;
var lineNumber = consoleMessage.lineNumber;
// Sometimes, a scripterror instance won't have any flags set. We
// default to exception.
if (!flags) {
flags = Ci.nsIScriptError.exceptionFlag;
}
// Default the filename and line number if they aren't set.
if (!sourceName) {
sourceName = "<unknown>";
}
if (!lineNumber) {
lineNumber = "<unknown>";
}
// Report the error in the log file.
if (flags & Ci.nsIScriptError.warningFlag) {
// Only report warnings if the error level is warning or better.
if (errorLevel == G_DebugService.ERROR_LEVEL_WARNING ||
errorLevel == G_DebugService.ERROR_LEVEL_INFO) {
this.reportScriptError_(consoleMessage.message,
sourceName,
lineNumber,
G_DebugService.ERROR_LEVEL_WARNING);
}
} else if (flags & Ci.nsIScriptError.exceptionFlag) {
// Always report exceptions.
this.reportScriptError_(consoleMessage.message,
sourceName,
lineNumber,
G_DebugService.ERROR_LEVEL_EXCEPTION);
}
}
}
/**
* Private helper to report an nsIScriptError instance to the log/console.
*/
G_DebugService.prototype.reportScriptError_ = function(message, sourceName,
lineNumber, label) {
message = "\n------------------------------------------------------------\n" +
label + ": " + message +
"\nlocation: " + sourceName + ", " + "line: " + lineNumber +
"\n------------------------------------------------------------\n\n";
dump(message);
this.maybeDumpToFile(message);
}
/**
* A class that instruments methods so they output a call trace,
* including the values of their actual parameters and return value.
* This code is mostly stolen from Aaron Boodman's original
* implementation in clobber utils.
*
* Note that this class uses the "loggifier" debug zone, so you'll see
* a complete call trace when that zone is enabled.
*
* @constructor
*/
this.G_Loggifier = function G_Loggifier() {
if (G_GDEBUG) {
// Careful not to loggify ourselves!
this.mark_(this);
}
}
/**
* Marks an object as having been loggified. Loggification is not
* idempotent :)
*
* @param obj Object to be marked
*/
G_Loggifier.prototype.mark_ = function(obj) {
if (G_GDEBUG) {
obj.__loggified_ = true;
}
}
/**
* @param obj Object to be examined
* @returns Boolean indicating if the object has been loggified
*/
G_Loggifier.prototype.isLoggified = function(obj) {
if (G_GDEBUG) {
return !!obj.__loggified_;
}
}
/**
* Attempt to extract the class name from the constructor definition.
* Assumes the object was created using new.
*
* @param constructor String containing the definition of a constructor,
* for example what you'd get by examining obj.constructor
* @returns Name of the constructor/object if it could be found, else "???"
*/
G_Loggifier.prototype.getFunctionName_ = function(constructor) {
if (G_GDEBUG) {
return constructor.name || "???";
}
}
/**
* Wraps all the methods in an object so that call traces are
* automatically outputted.
*
* @param obj Object to loggify. SHOULD BE THE PROTOTYPE OF A USER-DEFINED
* object. You can get into trouble if you attempt to
* loggify something that isn't, for example the Window.
*
* Any additional parameters are considered method names which should not be
* loggified.
*
* Usage:
* G_debugService.loggifier.loggify(MyClass.prototype,
* "firstMethodNotToLog",
* "secondMethodNotToLog",
* ... etc ...);
*/
G_Loggifier.prototype.loggify = function(obj) {
if (G_GDEBUG) {
if (!G_debugService.callTracingEnabled()) {
return;
}
if (typeof window != "undefined" && obj == window ||
this.isLoggified(obj)) // Don't go berserk!
return;
var zone = G_GetDebugZone(obj);
if (!zone || !zone.zoneIsEnabled()) {
return;
}
this.mark_(obj);
// Helper function returns an instrumented version of
// objName.meth, with "this" bound properly. (BTW, because we're
// in a conditional here, functions will only be defined as
// they're encountered during execution, so declare this helper
// before using it.)
let wrap = function (meth, objName, methName) {
return function() {
// First output the call along with actual parameters
var args = new Array(arguments.length);
var argsString = "";
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
argsString += (i == 0 ? "" : ", ");
if (typeof args[i] == "function") {
argsString += "[function]";
} else {
argsString += args[i];
}
}
G_TraceCall(this, "> " + objName + "." + methName + "(" +
argsString + ")");
// Then run the function, capturing the return value and throws
try {
var retVal = meth.apply(this, arguments);
var reportedRetVal = retVal;
if (typeof reportedRetVal == "undefined")
reportedRetVal = "void";
else if (reportedRetVal === "")
reportedRetVal = "\"\" (empty string)";
} catch (e) {
if (e && !e.__logged) {
G_TraceCall(this, "Error: " + e.message + ". " +
e.fileName + ": " + e.lineNumber);
try {
e.__logged = true;
} catch (e2) {
// Sometimes we can't add the __logged flag because it's an
// XPC wrapper
throw e;
}
}
throw e; // Re-throw!
}
// And spit it out already
G_TraceCall(
this,
"< " + objName + "." + methName + ": " + reportedRetVal);
return retVal;
};
};
var ignoreLookup = {};
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
ignoreLookup[arguments[i]] = true;
}
}
// Wrap each method of obj
for (var p in obj) {
// Work around bug in Firefox. In ffox typeof RegExp is "function",
// so make sure this really is a function. Bug as of FFox 1.5b2.
if (typeof obj[p] == "function" && obj[p].call && !ignoreLookup[p]) {
var objName = this.getFunctionName_(obj.constructor);
obj[p] = wrap(obj[p], objName, p);
}
}
}
}
/**
* Simple abstraction around debug settings. The thing with debug settings is
* that we want to be able to specify a default in the application's startup,
* but have that default be overridable by the user via their prefs.
*
* To generalize this, we package up a dictionary of defaults with the
* preferences tree. If a setting isn't in the preferences tree, then we grab it
* from the defaults.
*/
this.G_DebugSettings = function G_DebugSettings() {
this.defaults_ = {};
this.prefs_ = new G_Preferences();
}
/**
* Returns the value of a settings, optionally defaulting to a given value if it
* doesn't exist. If no default is specified, the default is |undefined|.
*/
G_DebugSettings.prototype.getSetting = function(name, opt_default) {
var override = this.prefs_.getPref(name, null);
if (override !== null) {
return override;
} else if (typeof this.defaults_[name] != "undefined") {
return this.defaults_[name];
} else {
return opt_default;
}
}
/**
* Sets the default value for a setting. If the user doesn't override it with a
* preference, this is the value which will be returned by getSetting().
*/
G_DebugSettings.prototype.setDefault = function(name, val) {
this.defaults_[name] = val;
}
var G_debugService = new G_DebugService(); // Instantiate us!
if (G_GDEBUG) {
G_debugService.enableAllZones();
}
#else
// Stubs for the debugging aids scattered through this component.
// They will be expanded if you compile yourself a debug build.
this.G_Debug = function G_Debug(who, msg) { }
this.G_Assert = function G_Assert(who, condition, msg) { }
this.G_Error = function G_Error(who, msg) { }
this.G_debugService = { __noSuchMethod__: function() { } };
#endif
@@ -1,82 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
* lang.js - Some missing JavaScript language features
*/
/**
* Partially applies a function to a particular "this object" and zero or
* more arguments. The result is a new function with some arguments of the first
* function pre-filled and the value of |this| "pre-specified".
*
* Remaining arguments specified at call-time are appended to the pre-
* specified ones.
*
* Usage:
* var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2");
* barMethBound("arg3", "arg4");
*
* @param fn {string} Reference to the function to be bound
*
* @param self {object} Specifies the object which |this| should point to
* when the function is run. If the value is null or undefined, it will default
* to the global object.
*
* @returns {function} A partially-applied form of the speficied function.
*/
this.BindToObject = function BindToObject(fn, self, opt_args) {
var boundargs = fn.boundArgs_ || [];
boundargs = boundargs.concat(Array.slice(arguments, 2, arguments.length));
if (fn.boundSelf_)
self = fn.boundSelf_;
if (fn.boundFn_)
fn = fn.boundFn_;
var newfn = function() {
// Combine the static args and the new args into one big array
var args = boundargs.concat(Array.slice(arguments));
return fn.apply(self, args);
}
newfn.boundArgs_ = boundargs;
newfn.boundSelf_ = self;
newfn.boundFn_ = fn;
return newfn;
}
/**
* Inherit the prototype methods from one constructor into another.
*
* Usage:
*
* function ParentClass(a, b) { }
* ParentClass.prototype.foo = function(a) { }
*
* function ChildClass(a, b, c) {
* ParentClass.call(this, a, b);
* }
*
* ChildClass.inherits(ParentClass);
*
* var child = new ChildClass("a", "b", "see");
* child.foo(); // works
*
* In addition, a superclass' implementation of a method can be invoked
* as follows:
*
* ChildClass.prototype.foo = function(a) {
* ChildClass.superClass_.foo.call(this, a);
* // other code
* };
*/
Function.prototype.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
}
@@ -1,145 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// A couple of classes to simplify creating observers.
//
// // Example1:
//
// function doSomething() { ... }
// var observer = new G_ObserverWrapper(topic, doSomething);
// someObj.addObserver(topic, observer);
//
// // Example2:
//
// function doSomething() { ... }
// new G_ObserverServiceObserver("profile-after-change",
// doSomething,
// true /* run only once */);
/**
* This class abstracts the admittedly simple boilerplate required of
* an nsIObserver. It saves you the trouble of implementing the
* indirection of your own observe() function.
*
* @param topic String containing the topic the observer will filter for
*
* @param observeFunction Reference to the function to call when the
* observer fires
*
* @constructor
*/
this.G_ObserverWrapper = function G_ObserverWrapper(topic, observeFunction) {
this.debugZone = "observer";
this.topic_ = topic;
this.observeFunction_ = observeFunction;
}
/**
* XPCOM
*/
G_ObserverWrapper.prototype.QueryInterface = function(iid) {
if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
/**
* Invoked by the thingy being observed
*/
G_ObserverWrapper.prototype.observe = function(subject, topic, data) {
if (topic == this.topic_)
this.observeFunction_(subject, topic, data);
}
/**
* This class abstracts the admittedly simple boilerplate required of
* observing an observerservice topic. It implements the indirection
* required, and automatically registers to hear the topic.
*
* @param topic String containing the topic the observer will filter for
*
* @param observeFunction Reference to the function to call when the
* observer fires
*
* @param opt_onlyOnce Boolean indicating if the observer should unregister
* after it has fired
*
* @constructor
*/
this.G_ObserverServiceObserver =
function G_ObserverServiceObserver(topic, observeFunction, opt_onlyOnce) {
this.debugZone = "observerserviceobserver";
this.topic_ = topic;
this.observeFunction_ = observeFunction;
this.onlyOnce_ = !!opt_onlyOnce;
this.observer_ = new G_ObserverWrapper(this.topic_,
BindToObject(this.observe_, this));
this.observerService_ = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
this.observerService_.addObserver(this.observer_, this.topic_, false);
}
/**
* Unregister the observer from the observerservice
*/
G_ObserverServiceObserver.prototype.unregister = function() {
this.observerService_.removeObserver(this.observer_, this.topic_);
this.observerService_ = null;
}
/**
* Invoked by the observerservice
*/
G_ObserverServiceObserver.prototype.observe_ = function(subject, topic, data) {
this.observeFunction_(subject, topic, data);
if (this.onlyOnce_)
this.unregister();
}
#ifdef DEBUG
this.TEST_G_Observer = function TEST_G_Observer() {
if (G_GDEBUG) {
var z = "observer UNITTEST";
G_debugService.enableZone(z);
G_Debug(z, "Starting");
var regularObserverRan = 0;
var observerServiceObserverRan = 0;
let regularObserver = function () {
regularObserverRan++;
};
let observerServiceObserver = function () {
observerServiceObserverRan++;
};
var service = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
var topic = "google-observer-test";
var o1 = new G_ObserverWrapper(topic, regularObserver);
service.addObserver(o1, topic, false);
new G_ObserverServiceObserver(topic,
observerServiceObserver, true /* once */);
// Notifications happen synchronously, so this is easy
service.notifyObservers(null, topic, null);
service.notifyObservers(null, topic, null);
G_Assert(z, regularObserverRan == 2, "Regular observer broken");
G_Assert(z, observerServiceObserverRan == 1, "ObsServObs broken");
service.removeObserver(o1, topic);
G_Debug(z, "PASSED");
}
}
#endif
@@ -1,276 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Class for manipulating preferences. Aside from wrapping the pref
// service, useful functionality includes:
//
// - abstracting prefobserving so that you can observe preferences
// without implementing nsIObserver
//
// - getters that return a default value when the pref doesn't exist
// (instead of throwing)
//
// - get-and-set getters
//
// Example:
//
// var p = new PROT_Preferences();
// dump(p.getPref("some-true-pref")); // shows true
// dump(p.getPref("no-such-pref", true)); // shows true
// dump(p.getPref("no-such-pref", null)); // shows null
//
// function observe(prefThatChanged) {
// dump("Pref changed: " + prefThatChanged);
// };
//
// p.addObserver("somepref", observe);
// p.setPref("somepref", true); // dumps
// p.removeObserver("somepref", observe);
//
// TODO: should probably have the prefobserver pass in the new and old
// values
// TODO(tc): Maybe remove this class and just call natively since we're no
// longer an extension.
/**
* A class that wraps the preferences service.
*
* @param opt_startPoint A starting point on the prefs tree to resolve
* names passed to setPref and getPref.
*
* @param opt_useDefaultPranch Set to true to work against the default
* preferences tree instead of the profile one.
*
* @constructor
*/
this.G_Preferences =
function G_Preferences(opt_startPoint, opt_getDefaultBranch) {
this.debugZone = "prefs";
this.observers_ = {};
this.getDefaultBranch_ = !!opt_getDefaultBranch;
this.startPoint_ = opt_startPoint || null;
}
G_Preferences.setterMap_ = { "string": "setCharPref",
"boolean": "setBoolPref",
"number": "setIntPref" };
G_Preferences.getterMap_ = {};
G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_STRING] = "getCharPref";
G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_BOOL] = "getBoolPref";
G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_INT] = "getIntPref";
G_Preferences.prototype.__defineGetter__('prefs_', function() {
var prefs;
var prefSvc = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService);
if (this.getDefaultBranch_) {
prefs = prefSvc.getDefaultBranch(this.startPoint_);
} else {
prefs = prefSvc.getBranch(this.startPoint_);
}
// QI to prefs in case we want to add observers
prefs.QueryInterface(Ci.nsIPrefBranchInternal);
return prefs;
});
/**
* Stores a key/value in a user preference. Valid types for val are string,
* boolean, and number. Complex values are not yet supported (but feel free to
* add them!).
*/
G_Preferences.prototype.setPref = function(key, val) {
var datatype = typeof(val);
if (datatype == "number" && (val % 1 != 0)) {
throw new Error("Cannot store non-integer numbers in preferences.");
}
var meth = G_Preferences.setterMap_[datatype];
if (!meth) {
throw new Error("Pref datatype {" + datatype + "} not supported.");
}
return this.prefs_[meth](key, val);
}
/**
* Retrieves a user preference. Valid types for the value are the same as for
* setPref. If the preference is not found, opt_default will be returned
* instead.
*/
G_Preferences.prototype.getPref = function(key, opt_default) {
var type = this.prefs_.getPrefType(key);
// zero means that the specified pref didn't exist
if (type == Ci.nsIPrefBranch.PREF_INVALID) {
return opt_default;
}
var meth = G_Preferences.getterMap_[type];
if (!meth) {
throw new Error("Pref datatype {" + type + "} not supported.");
}
// If a pref has been cleared, it will have a valid type but won't
// be gettable, so this will throw.
try {
return this.prefs_[meth](key);
} catch(e) {
return opt_default;
}
}
/**
* Delete a preference.
*
* @param which Name of preference to obliterate
*/
G_Preferences.prototype.clearPref = function(which) {
try {
// This throws if the pref doesn't exist, which is fine because a
// nonexistent pref is cleared
this.prefs_.clearUserPref(which);
} catch(e) {}
}
/**
* Add an observer for a given pref.
*
* @param which String containing the pref to listen to
* @param callback Function to be called when the pref changes. This
* function will receive a single argument, a string
* holding the preference name that changed
*/
G_Preferences.prototype.addObserver = function(which, callback) {
// Need to store the observer we create so we can eventually unregister it
if (!this.observers_[which])
this.observers_[which] = { callbacks: [], observers: [] };
/* only add an observer if the callback hasn't been registered yet */
if (this.observers_[which].callbacks.indexOf(callback) == -1) {
var observer = new G_PreferenceObserver(callback);
this.observers_[which].callbacks.push(callback);
this.observers_[which].observers.push(observer);
this.prefs_.addObserver(which, observer, false /* strong reference */);
}
}
/**
* Remove an observer for a given pref.
*
* @param which String containing the pref to stop listening to
* @param callback Function to remove as an observer
*/
G_Preferences.prototype.removeObserver = function(which, callback) {
var ix = this.observers_[which].callbacks.indexOf(callback);
G_Assert(this, ix != -1, "Tried to unregister a nonexistent observer");
this.observers_[which].callbacks.splice(ix, 1);
var observer = this.observers_[which].observers.splice(ix, 1)[0];
this.prefs_.removeObserver(which, observer);
}
/**
* Remove all preference observers registered through this object.
*/
G_Preferences.prototype.removeAllObservers = function() {
for (var which in this.observers_) {
for each (var observer in this.observers_[which].observers) {
this.prefs_.removeObserver(which, observer);
}
}
this.observers_ = {};
}
/**
* Helper class that knows how to observe preference changes and
* invoke a callback when they do
*
* @constructor
* @param callback Function to call when the preference changes
*/
this.G_PreferenceObserver =
function G_PreferenceObserver(callback) {
this.debugZone = "prefobserver";
this.callback_ = callback;
}
/**
* Invoked by the pref system when a preference changes. Passes the
* message along to the callback.
*
* @param subject The nsIPrefBranch that changed
* @param topic String "nsPref:changed" (aka
* NS_PREFBRANCH_PREFCHANGE_OBSERVER_ID -- but where does it
* live???)
* @param data Name of the pref that changed
*/
G_PreferenceObserver.prototype.observe = function(subject, topic, data) {
G_Debug(this, "Observed pref change: " + data);
this.callback_(data);
}
/**
* XPCOM cruft
*
* @param iid Interface id of the interface the caller wants
*/
G_PreferenceObserver.prototype.QueryInterface = function(iid) {
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIObserver) ||
iid.equals(Ci.nsISupportsWeakReference))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
#ifdef DEBUG
// UNITTESTS
this.TEST_G_Preferences = function TEST_G_Preferences() {
if (G_GDEBUG) {
var z = "preferences UNITTEST";
G_debugService.enableZone(z);
G_Debug(z, "Starting");
var p = new G_Preferences();
var testPref = "test-preferences-unittest";
var noSuchPref = "test-preferences-unittest-aypabtu";
// Used to test observing
var observeCount = 0;
let observe = function (prefChanged) {
G_Assert(z, prefChanged == testPref, "observer broken");
observeCount++;
};
// Test setting, getting, and observing
p.addObserver(testPref, observe);
p.setPref(testPref, true);
G_Assert(z, p.getPref(testPref), "get or set broken");
G_Assert(z, observeCount == 1, "observer adding not working");
p.removeObserver(testPref, observe);
p.setPref(testPref, false);
G_Assert(z, observeCount == 1, "observer removal not working");
G_Assert(z, !p.getPref(testPref), "get broken");
// Remember to clean up the prefs we've set, and test removing prefs
// while we're at it
p.clearPref(noSuchPref);
G_Assert(z, !p.getPref(noSuchPref, false), "clear broken");
p.clearPref(testPref);
G_Debug(z, "PASSED");
}
}
#endif
@@ -1,133 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// A helper class that knows how to parse from and serialize to
// protocol4. This is a simple, historical format used by some Google
// interfaces, for example the Toolbar (i.e., ancient services).
//
// Protocol4 consists of a newline-separated sequence of name/value
// pairs (strings). Each line consists of the name, the value length,
// and the value itself, all separated by colons. Example:
//
// foo:6:barbaz\n
// fritz:33:issickofdynamicallytypedlanguages\n
/**
* This class knows how to serialize/deserialize maps to/from their
* protocol4 representation.
*
* @constructor
*/
this.G_Protocol4Parser = function G_Protocol4Parser() {
this.debugZone = "protocol4";
this.protocol4RegExp_ = new RegExp("([^:]+):\\d+:(.*)$");
this.newlineRegExp_ = new RegExp("(\\r)?\\n");
}
/**
* Create a map from a protocol4 string. Silently skips invalid lines.
*
* @param text String holding the protocol4 representation
*
* @returns Object as an associative array with keys and values
* given in text. The empty object is returned if none
* are parsed.
*/
G_Protocol4Parser.prototype.parse = function(text) {
var response = {};
if (!text)
return response;
// Responses are protocol4: (repeated) name:numcontentbytes:content\n
var lines = text.split(this.newlineRegExp_);
for (var i = 0; i < lines.length; i++)
if (this.protocol4RegExp_.exec(lines[i]))
response[RegExp.$1] = RegExp.$2;
return response;
}
/**
* Create a protocol4 string from a map (object). Throws an error on
* an invalid input.
*
* @param map Object as an associative array with keys and values
* given as strings.
*
* @returns text String holding the protocol4 representation
*/
G_Protocol4Parser.prototype.serialize = function(map) {
if (typeof map != "object")
throw new Error("map must be an object");
var text = "";
for (var key in map) {
if (typeof map[key] != "string")
throw new Error("Keys and values must be strings");
text += key + ":" + map[key].length + ":" + map[key] + "\n";
}
return text;
}
#ifdef DEBUG
/**
* Cheesey unittests
*/
this.TEST_G_Protocol4Parser = function TEST_G_Protocol4Parser() {
if (G_GDEBUG) {
var z = "protocol4 UNITTEST";
G_debugService.enableZone(z);
G_Debug(z, "Starting");
var p = new G_Protocol4Parser();
let isEmpty = function (map) {
for (var key in map)
return false;
return true;
};
G_Assert(z, isEmpty(p.parse(null)), "Parsing null broken");
G_Assert(z, isEmpty(p.parse("")), "Parsing nothing broken");
var t = "foo:3:bar";
G_Assert(z, p.parse(t)["foo"] === "bar", "Parsing one line broken");
t = "foo:3:bar\n";
G_Assert(z, p.parse(t)["foo"] === "bar", "Parsing line with lf broken");
t = "foo:3:bar\r\n";
G_Assert(z, p.parse(t)["foo"] === "bar", "Parsing with crlf broken");
t = "foo:3:bar\nbar:3:baz\r\nbom:3:yaz\n";
G_Assert(z, p.parse(t)["foo"] === "bar", "First in multiline");
G_Assert(z, p.parse(t)["bar"] === "baz", "Second in multiline");
G_Assert(z, p.parse(t)["bom"] === "yaz", "Third in multiline");
G_Assert(z, p.parse(t)[""] === undefined, "Nonexistent in multiline");
// Test serialization
var original = {
"1": "1",
"2": "2",
"foobar": "baz",
"hello there": "how are you?" ,
};
var deserialized = p.parse(p.serialize(original));
for (var key in original)
G_Assert(z, original[key] === deserialized[key],
"Trouble (de)serializing " + key);
G_Debug(z, "PASSED");
}
}
#endif
@@ -1,137 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
* This class helps us batch a series of async calls to the db.
* If any of the tokens is in the database, we fire callback with
* true as a param. If all the tokens are not in the database,
* we fire callback with false as a param.
* This is an "Abstract" base class. Subclasses need to supply
* the condition_ method.
*
* @param tokens Array of strings to lookup in the db
* @param tableName String name of the table
* @param callback Function callback function that takes true if the condition
* passes.
*/
this.MultiQuerier =
function MultiQuerier(tokens, tableName, callback) {
this.tokens_ = tokens;
this.tableName_ = tableName;
this.callback_ = callback;
this.dbservice_ = Cc["@mozilla.org/url-classifier/dbservice;1"]
.getService(Ci.nsIUrlClassifierDBService);
// We put the current token in this variable.
this.key_ = null;
}
/**
* Run the remaining tokens against the db.
*/
MultiQuerier.prototype.run = function() {
if (this.tokens_.length == 0) {
this.callback_.handleEvent(false);
this.dbservice_ = null;
this.callback_ = null;
return;
}
this.key_ = this.tokens_.pop();
G_Debug(this, "Looking up " + this.key_ + " in " + this.tableName_);
this.dbservice_.exists(this.tableName_, this.key_,
BindToObject(this.result_, this));
}
/**
* Callback from the db. If the returned value passes the this.condition_
* test, go ahead and call the main callback.
*/
MultiQuerier.prototype.result_ = function(value) {
if (this.condition_(value)) {
this.callback_.handleEvent(true)
this.dbservice_ = null;
this.callback_ = null;
} else {
this.run();
}
}
// Subclasses must override this.
MultiQuerier.prototype.condition_ = function(value) {
throw "MultiQuerier is an abstract base class";
}
/**
* Concrete MultiQuerier that stops if the key exists in the db.
*/
this.ExistsMultiQuerier =
function ExistsMultiQuerier(tokens, tableName, callback) {
MultiQuerier.call(this, tokens, tableName, callback);
this.debugZone = "existsMultiQuerier";
}
ExistsMultiQuerier.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
}
ExistsMultiQuerier.inherits(MultiQuerier);
ExistsMultiQuerier.prototype.condition_ = function(value) {
return value.length > 0;
}
/**
* Concrete MultiQuerier that looks up a key, decrypts it, then
* checks the the resulting regular expressions for a match.
* @param tokens Array of hosts
*/
this.EnchashMultiQuerier =
function EnchashMultiQuerier(tokens, tableName, callback, url) {
MultiQuerier.call(this, tokens, tableName, callback);
this.url_ = url;
this.enchashDecrypter_ = new PROT_EnchashDecrypter();
this.debugZone = "enchashMultiQuerier";
}
EnchashMultiQuerier.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
}
EnchashMultiQuerier.inherits(MultiQuerier);
EnchashMultiQuerier.prototype.run = function() {
if (this.tokens_.length == 0) {
this.callback_.handleEvent(false);
this.dbservice_ = null;
this.callback_ = null;
return;
}
var host = this.tokens_.pop();
this.key_ = host;
var lookupKey = this.enchashDecrypter_.getLookupKey(host);
this.dbservice_.exists(this.tableName_, lookupKey,
BindToObject(this.result_, this));
}
EnchashMultiQuerier.prototype.condition_ = function(encryptedValue) {
if (encryptedValue.length > 0) {
// We have encrypted regular expressions for this host. Let's
// decrypt them and see if we have a match.
var decrypted = this.enchashDecrypter_.decryptData(encryptedValue,
this.key_);
var res = this.enchashDecrypter_.parseRegExps(decrypted);
for (var j = 0; j < res.length; j++) {
if (res[j].test(this.url_)) {
return true;
}
}
}
return false;
}
@@ -1,116 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This implements logic for stopping requests if the server starts to return
// too many errors. If we get MAX_ERRORS errors in ERROR_PERIOD minutes, we
// back off for TIMEOUT_INCREMENT minutes. If we get another error
// immediately after we restart, we double the timeout and add
// TIMEOUT_INCREMENT minutes, etc.
//
// This is similar to the logic used by the search suggestion service.
// HTTP responses that count as an error. We also include any 5xx response
// as an error.
this.HTTP_FOUND = 302;
this.HTTP_SEE_OTHER = 303;
this.HTTP_TEMPORARY_REDIRECT = 307;
/**
* @param maxErrors Number of times to request before backing off.
* @param retryIncrement Time (ms) for each retry before backing off.
* @param maxRequests Number the number of requests needed to trigger backoff
* @param requestPeriod Number time (ms) in which maxRequests have to occur to
* trigger the backoff behavior (0 to disable maxRequests)
* @param timeoutIncrement Number time (ms) the starting timeout period
* we double this time for consecutive errors
* @param maxTimeout Number time (ms) maximum timeout period
*/
this.RequestBackoff =
function RequestBackoff(maxErrors, retryIncrement,
maxRequests, requestPeriod,
timeoutIncrement, maxTimeout) {
this.MAX_ERRORS_ = maxErrors;
this.RETRY_INCREMENT_ = retryIncrement;
this.MAX_REQUESTS_ = maxRequests;
this.REQUEST_PERIOD_ = requestPeriod;
this.TIMEOUT_INCREMENT_ = timeoutIncrement;
this.MAX_TIMEOUT_ = maxTimeout;
// Queue of ints keeping the time of all requests
this.requestTimes_ = [];
this.numErrors_ = 0;
this.errorTimeout_ = 0;
this.nextRequestTime_ = 0;
}
/**
* Reset the object for reuse. This deliberately doesn't clear requestTimes_.
*/
RequestBackoff.prototype.reset = function() {
this.numErrors_ = 0;
this.errorTimeout_ = 0;
this.nextRequestTime_ = 0;
}
/**
* Check to see if we can make a request.
*/
RequestBackoff.prototype.canMakeRequest = function() {
var now = Date.now();
if (now < this.nextRequestTime_) {
return false;
}
return (this.requestTimes_.length < this.MAX_REQUESTS_ ||
(now - this.requestTimes_[0]) > this.REQUEST_PERIOD_);
}
RequestBackoff.prototype.noteRequest = function() {
var now = Date.now();
this.requestTimes_.push(now);
// We only care about keeping track of MAX_REQUESTS
if (this.requestTimes_.length > this.MAX_REQUESTS_)
this.requestTimes_.shift();
}
RequestBackoff.prototype.nextRequestDelay = function() {
return Math.max(0, this.nextRequestTime_ - Date.now());
}
/**
* Notify this object of the last server response. If it's an error,
*/
RequestBackoff.prototype.noteServerResponse = function(status) {
if (this.isErrorStatus(status)) {
this.numErrors_++;
if (this.numErrors_ < this.MAX_ERRORS_)
this.errorTimeout_ = this.RETRY_INCREMENT_;
else if (this.numErrors_ == this.MAX_ERRORS_)
this.errorTimeout_ = this.TIMEOUT_INCREMENT_;
else
this.errorTimeout_ *= 2;
this.errorTimeout_ = Math.min(this.errorTimeout_, this.MAX_TIMEOUT_);
this.nextRequestTime_ = Date.now() + this.errorTimeout_;
} else {
// Reset error timeout, allow requests to go through.
this.reset();
}
}
/**
* We consider 302, 303, 307, 4xx, and 5xx http responses to be errors.
* @param status Number http status
* @return Boolean true if we consider this http status an error
*/
RequestBackoff.prototype.isErrorStatus = function(status) {
return ((400 <= status && status <= 599) ||
HTTP_FOUND == status ||
HTTP_SEE_OTHER == status ||
HTTP_TEMPORARY_REDIRECT == status);
}
@@ -1,169 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// XXX: This should all be moved into the dbservice class so it happens
// in the background thread.
/**
* Abstract base class for a lookup table.
* @construction
*/
this.UrlClassifierTable = function UrlClassifierTable() {
this.debugZone = "urlclassifier-table";
this.name = '';
this.needsUpdate = false;
this.enchashDecrypter_ = new PROT_EnchashDecrypter();
this.wrappedJSObject = this;
}
UrlClassifierTable.prototype.QueryInterface = function(iid) {
if (iid.equals(Components.interfaces.nsISupports) ||
iid.equals(Components.interfaces.nsIUrlClassifierTable))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
/**
* Subclasses need to implement this method.
*/
UrlClassifierTable.prototype.exists = function(url, callback) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
/////////////////////////////////////////////////////////////////////
// Url table implementation
this.UrlClassifierTableUrl = function UrlClassifierTableUrl() {
UrlClassifierTable.call(this);
}
UrlClassifierTableUrl.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
}
UrlClassifierTableUrl.inherits(UrlClassifierTable);
/**
* Look up a URL in a URL table
*/
UrlClassifierTableUrl.prototype.exists = function(url, callback) {
// nsIUrlClassifierUtils.canonicalizeURL is the old way of canonicalizing a
// URL. Unfortunately, it doesn't normalize numeric domains so alternate IP
// formats (hex, octal, etc) won't trigger a match.
// this.enchashDecrypter_.getCanonicalUrl does the right thing and
// normalizes a URL to 4 decimal numbers, but the update server may still be
// giving us encoded IP addresses. So to be safe, we check both cases.
var urlUtils = Cc["@mozilla.org/url-classifier/utils;1"]
.getService(Ci.nsIUrlClassifierUtils);
var oldCanonicalized = urlUtils.canonicalizeURL(url);
var canonicalized = this.enchashDecrypter_.getCanonicalUrl(url);
G_Debug(this, "Looking up: " + url + " (" + oldCanonicalized + " and " +
canonicalized + ")");
(new ExistsMultiQuerier([oldCanonicalized, canonicalized],
this.name,
callback)).run();
}
/////////////////////////////////////////////////////////////////////
// Domain table implementation
this.UrlClassifierTableDomain = function UrlClassifierTableDomain() {
UrlClassifierTable.call(this);
this.debugZone = "urlclassifier-table-domain";
this.ioService_ = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
}
UrlClassifierTableDomain.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
}
UrlClassifierTableDomain.inherits(UrlClassifierTable);
/**
* Look up a URL in a domain table
* We also try to lookup domain + first path component (e.g.,
* www.mozilla.org/products).
*
* @returns Boolean true if the url domain is in the table
*/
UrlClassifierTableDomain.prototype.exists = function(url, callback) {
var canonicalized = this.enchashDecrypter_.getCanonicalUrl(url);
var urlObj = this.ioService_.newURI(canonicalized, null, null);
var host = '';
try {
host = urlObj.host;
} catch (e) { }
var hostComponents = host.split(".");
// Try to get the path of the URL. Pseudo urls (like wyciwyg:) throw
// errors when trying to convert to an nsIURL so we wrap in a try/catch
// block.
var path = ""
try {
urlObj.QueryInterface(Ci.nsIURL);
path = urlObj.filePath;
} catch (e) { }
var pathComponents = path.split("/");
// We don't have a good way map from hosts to domains, so we instead try
// each possibility. Could probably optimize to start at the second dot?
var possible = [];
for (var i = 0; i < hostComponents.length - 1; i++) {
host = hostComponents.slice(i).join(".");
possible.push(host);
// The path starts with a "/", so we are interested in the second path
// component if it is available
if (pathComponents.length >= 2 && pathComponents[1].length > 0) {
host = host + "/" + pathComponents[1];
possible.push(host);
}
}
// Run the possible domains against the db.
(new ExistsMultiQuerier(possible, this.name, callback)).run();
}
/////////////////////////////////////////////////////////////////////
// Enchash table implementation
this.UrlClassifierTableEnchash = function UrlClassifierTableEnchash() {
UrlClassifierTable.call(this);
this.debugZone = "urlclassifier-table-enchash";
}
UrlClassifierTableEnchash.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
}
UrlClassifierTableEnchash.inherits(UrlClassifierTable);
/**
* Look up a URL in an enchashDB. We try all sub domains (up to MAX_DOTS).
*/
UrlClassifierTableEnchash.prototype.exists = function(url, callback) {
url = this.enchashDecrypter_.getCanonicalUrl(url);
var host = this.enchashDecrypter_.getCanonicalHost(url,
PROT_EnchashDecrypter.MAX_DOTS);
var possible = [];
for (var i = 0; i < PROT_EnchashDecrypter.MAX_DOTS + 1; i++) {
possible.push(host);
var index = host.indexOf(".");
if (index == -1)
break;
host = host.substring(index + 1);
}
// Run the possible domains against the db.
(new EnchashMultiQuerier(possible, this.name, callback, url)).run();
}
@@ -1,230 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// A class that serializes and deserializes opaque key/value string to
// string maps to/from maps (trtables). It knows how to create
// trtables from the serialized format, so it also understands
// meta-information like the name of the table and the table's
// version. See docs for the protocol description.
//
// TODO: wireformatreader: if you have multiple updates for one table
// in a call to deserialize, the later ones will be merged
// (all but the last will be ignored). To fix, merge instead
// of replace when you have an existing table, and only do so once.
// TODO must have blank line between successive types -- problem?
// TODO doesn't tolerate blank lines very well
//
// Maybe: These classes could use a LOT more cleanup, but it's not a
// priority at the moment. For example, the tablesData/Known
// maps should be combined into a single object, the parser
// for a given type should be separate from the version info,
// and there should be synchronous interfaces for testing.
/**
* A class that knows how to serialize and deserialize meta-information.
* This meta information is the table name and version number, and
* in its serialized form looks like the first line below:
*
* [name-of-table X.Y update?]
* ...key/value pairs to add or delete follow...
* <blank line ends the table>
*
* The X.Y is the version number and the optional "update" token means
* that the table is a differential from the curent table the extension
* has. Its absence means that this is a full, new table.
*/
this.PROT_VersionParser =
function PROT_VersionParser(type, opt_major, opt_minor, opt_requireMac) {
this.debugZone = "versionparser";
this.type = type;
this.major = 0;
this.minor = 0;
this.badHeader = false;
// Should the wireformatreader compute a mac?
this.mac = false;
this.macval = "";
this.macFailed = false;
this.requireMac = !!opt_requireMac;
this.update = false;
this.needsUpdate = false; // used by ListManager to determine update policy
// Used by ListerManager to see if we have read data for this table from
// disk. Once we read a table from disk, we are not going to do so again
// but instead update remotely if necessary.
this.didRead = false;
if (opt_major)
this.major = parseInt(opt_major);
if (opt_minor)
this.minor = parseInt(opt_minor);
}
/** Import the version information from another VersionParser
* @params version a version parser object
*/
PROT_VersionParser.prototype.ImportVersion = function(version) {
this.major = version.major;
this.minor = version.minor;
this.mac = version.mac;
this.macFailed = version.macFailed;
this.macval = version.macval;
// Don't set requireMac, since we create vparsers from scratch and doesn't
// know about it
}
/**
* Creates a string like [goog-white-black 1.1] from internal information
*
* @returns String
*/
PROT_VersionParser.prototype.toString = function() {
var s = "[" + this.type + " " + this.major + "." + this.minor + "]";
return s;
}
/**
* Creates a string like 1.123 with the version number. This is the
* format we store in prefs.
* @return String
*/
PROT_VersionParser.prototype.versionString = function() {
return this.major + "." + this.minor;
}
/**
* Creates a string like 1:1 from internal information used for
* fetching updates from the server. Called by the listmanager.
*
* @returns String
*/
PROT_VersionParser.prototype.toUrl = function() {
return this.major + ":" + this.minor;
}
/**
* Process the old format, [type major.minor [update]]
*
* @returns true if the string could be parsed, false otherwise
*/
PROT_VersionParser.prototype.processOldFormat_ = function(line) {
if (line[0] != '[' || line.slice(-1) != ']')
return false;
var description = line.slice(1, -1);
// Get the type name and version number of this table
var tokens = description.split(" ");
this.type = tokens[0];
var majorminor = tokens[1].split(".");
this.major = parseInt(majorminor[0]);
this.minor = parseInt(majorminor[1]);
if (isNaN(this.major) || isNaN(this.minor))
return false;
if (tokens.length >= 3) {
this.update = tokens[2] == "update";
}
return true;
}
/**
* Takes a string like [name-of-table 1.1 [update]][mac=MAC] and figures out the
* type and corresponding version numbers.
* @returns true if the string could be parsed, false otherwise
*/
PROT_VersionParser.prototype.fromString = function(line) {
G_Debug(this, "Calling fromString with line: " + line);
if (line[0] != '[' || line.slice(-1) != ']')
return false;
// There could be two [][], so take care of it
var secondBracket = line.indexOf('[', 1);
var firstPart = null;
var secondPart = null;
if (secondBracket != -1) {
firstPart = line.substring(0, secondBracket);
secondPart = line.substring(secondBracket);
G_Debug(this, "First part: " + firstPart + " Second part: " + secondPart);
} else {
firstPart = line;
G_Debug(this, "Old format: " + firstPart);
}
if (!this.processOldFormat_(firstPart))
return false;
if (secondPart && !this.processOptTokens_(secondPart))
return false;
return true;
}
/**
* Process optional tokens
*
* @param line A string [token1=val1 token2=val2...]
* @returns true if the string could be parsed, false otherwise
*/
PROT_VersionParser.prototype.processOptTokens_ = function(line) {
if (line[0] != '[' || line.slice(-1) != ']')
return false;
var description = line.slice(1, -1);
// Get the type name and version number of this table
var tokens = description.split(" ");
for (var i = 0; i < tokens.length; i++) {
G_Debug(this, "Processing optional token: " + tokens[i]);
var tokenparts = tokens[i].split("=");
switch(tokenparts[0]){
case "mac":
this.mac = true;
if (tokenparts.length < 2) {
G_Debug(this, "Found mac flag but not mac value!");
return false;
}
// The mac value may have "=" in it, so we can't just use tokenparts[1].
// Instead, just take the rest of tokens[i] after the first "="
this.macval = tokens[i].substr(tokens[i].indexOf("=")+1);
break;
default:
G_Debug(this, "Found unrecognized token: " + tokenparts[0]);
break;
}
}
return true;
}
#ifdef DEBUG
this.TEST_PROT_WireFormat = function TEST_PROT_WireFormat() {
if (G_GDEBUG) {
var z = "versionparser UNITTEST";
G_Debug(z, "Starting");
var vp = new PROT_VersionParser("dummy");
G_Assert(z, vp.fromString("[foo-bar-url 1.234]"),
"failed to parse old format");
G_Assert(z, "foo-bar-url" == vp.type, "failed to parse type");
G_Assert(z, "1" == vp.major, "failed to parse major");
G_Assert(z, "234" == vp.minor, "failed to parse minor");
vp = new PROT_VersionParser("dummy");
G_Assert(z, vp.fromString("[foo-bar-url 1.234][mac=567]"),
"failed to parse new format");
G_Assert(z, "foo-bar-url" == vp.type, "failed to parse type");
G_Assert(z, "1" == vp.major, "failed to parse major");
G_Assert(z, "234" == vp.minor, "failed to parse minor");
G_Assert(z, true == vp.mac, "failed to parse mac");
G_Assert(z, "567" == vp.macval, "failed to parse macval");
G_Debug(z, "PASSED");
}
}
#endif
@@ -1,122 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// A simple class that encapsulates a request. You'll notice the
// style here is different from the rest of the extension; that's
// because this was re-used from really old code we had. At some
// point it might be nice to replace this with something better
// (e.g., something that has explicit onerror handler, ability
// to set headers, and so on).
/**
* Because we might be in a component, we can't just assume that
* XMLHttpRequest exists. So we use this tiny factory function to wrap the
* XPCOM version.
*
* @return XMLHttpRequest object
*/
this.PROT_NewXMLHttpRequest = function PROT_NewXMLHttpRequest() {
var Cc = Components.classes;
var Ci = Components.interfaces;
var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
// Need the following so we get onerror/load/progresschange
request.QueryInterface(Ci.nsIJSXMLHttpRequest);
return request;
}
/**
* A helper class that does HTTP GETs and calls back a function with
* the content it receives. Asynchronous, so uses a closure for the
* callback.
*
* Note, that XMLFetcher is only used for SafeBrowsing, therefore
* we inherit from nsILoadContext, so we can use the callbacks on the
* channel to separate the safebrowsing cookie based on a reserved
* appId.
* @constructor
*/
this.PROT_XMLFetcher = function PROT_XMLFetcher() {
this.debugZone = "xmlfetcher";
this._request = PROT_NewXMLHttpRequest();
// implements nsILoadContext
this.appId = Ci.nsIScriptSecurityManager.SAFEBROWSING_APP_ID;
this.isInBrowserElement = false;
this.usePrivateBrowsing = false;
this.isContent = false;
}
PROT_XMLFetcher.prototype = {
/**
* Function that will be called back upon fetch completion.
*/
_callback: null,
/**
* Fetches some content.
*
* @param page URL to fetch
* @param callback Function to call back when complete.
*/
get: function(page, callback) {
this._request.abort(); // abort() is asynchronous, so
this._request = PROT_NewXMLHttpRequest();
this._callback = callback;
var asynchronous = true;
this._request.open("GET", page, asynchronous);
this._request.channel.notificationCallbacks = this;
// Create a closure
var self = this;
this._request.addEventListener("readystatechange", function() {
self.readyStateChange(self);
}, false);
this._request.send(null);
},
cancel: function() {
this._request.abort();
this._request = null;
},
/**
* Called periodically by the request to indicate some state change. 4
* means content has been received.
*/
readyStateChange: function(fetcher) {
if (fetcher._request.readyState != 4)
return;
// If the request fails, on trunk we get status set to
// NS_ERROR_NOT_AVAILABLE. On 1.8.1 branch we get an exception
// forwarded from nsIHttpChannel::GetResponseStatus. To be consistent
// between branch and trunk, we send back NS_ERROR_NOT_AVAILABLE for
// http failures.
var responseText = null;
var status = Components.results.NS_ERROR_NOT_AVAILABLE;
try {
G_Debug(this, "xml fetch status code: \"" +
fetcher._request.status + "\"");
status = fetcher._request.status;
responseText = fetcher._request.responseText;
} catch(e) {
G_Debug(this, "Caught exception trying to read xmlhttprequest " +
"status/response.");
G_Debug(this, e);
}
if (fetcher._callback)
fetcher._callback(responseText, status);
},
// nsIInterfaceRequestor
getInterface: function(iid) {
return this.QueryInterface(iid);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor,
Ci.nsISupports,
Ci.nsILoadContext])
};
@@ -1,74 +0,0 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
TEST_DIRS += ['tests']
XPIDL_SOURCES += [
'nsIUrlClassifierDBService.idl',
'nsIUrlClassifierHashCompleter.idl',
'nsIUrlClassifierPrefixSet.idl',
'nsIUrlClassifierStreamUpdater.idl',
'nsIUrlClassifierUtils.idl',
'nsIUrlListManager.idl',
]
XPIDL_MODULE = 'url-classifier'
UNIFIED_SOURCES += [
'ChunkSet.cpp',
'Classifier.cpp',
'LookupCache.cpp',
'nsCheckSummedOutputStream.cpp',
'nsUrlClassifierDBService.cpp',
'nsUrlClassifierProxies.cpp',
'nsUrlClassifierUtils.cpp',
'ProtocolParser.cpp',
]
# define conflicting LOG() macros
SOURCES += [
'nsUrlClassifierPrefixSet.cpp',
'nsUrlClassifierStreamUpdater.cpp',
]
# contains variables that conflict with LookupCache.cpp
SOURCES += [
'HashStore.cpp',
]
EXTRA_COMPONENTS += [
'nsURLClassifier.manifest',
'nsUrlClassifierHashCompleter.js',
]
# Same as JS components that are run through the pre-processor.
EXTRA_PP_COMPONENTS += [
'nsUrlClassifierLib.js',
'nsUrlClassifierListManager.js',
]
EXTRA_JS_MODULES += [
'SafeBrowsing.jsm',
]
EXPORTS += [
'Entries.h',
'LookupCache.h',
'nsUrlClassifierPrefixSet.h',
]
FAIL_ON_WARNINGS = True
MSVC_ENABLE_PGO = True
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'../build',
'/ipc/chromium/src',
]
CXXFLAGS += CONFIG['SQLITE_CFLAGS']
@@ -1,59 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsILocalFile.h"
#include "nsCRT.h"
#include "nsIFile.h"
#include "nsISupportsImpl.h"
#include "nsCheckSummedOutputStream.h"
////////////////////////////////////////////////////////////////////////////////
// nsCheckSummedOutputStream
NS_IMPL_ISUPPORTS_INHERITED(nsCheckSummedOutputStream,
nsSafeFileOutputStream,
nsISafeOutputStream,
nsIOutputStream,
nsIFileOutputStream)
NS_IMETHODIMP
nsCheckSummedOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
int32_t behaviorFlags)
{
nsresult rv;
mHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mHash->Init(nsICryptoHash::MD5);
NS_ENSURE_SUCCESS(rv, rv);
return nsSafeFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
}
NS_IMETHODIMP
nsCheckSummedOutputStream::Finish()
{
nsresult rv = mHash->Finish(false, mCheckSum);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t written;
rv = nsSafeFileOutputStream::Write(reinterpret_cast<const char*>(mCheckSum.BeginReading()),
mCheckSum.Length(), &written);
NS_ASSERTION(written == mCheckSum.Length(), "Error writing stream checksum");
NS_ENSURE_SUCCESS(rv, rv);
return nsSafeFileOutputStream::Finish();
}
NS_IMETHODIMP
nsCheckSummedOutputStream::Write(const char *buf, uint32_t count, uint32_t *result)
{
nsresult rv = mHash->Update(reinterpret_cast<const uint8_t*>(buf), count);
NS_ENSURE_SUCCESS(rv, rv);
return nsSafeFileOutputStream::Write(buf, count, result);
}
////////////////////////////////////////////////////////////////////////////////
@@ -1,54 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsCheckSummedOutputStream_h__
#define nsCheckSummedOutputStream_h__
#include "nsILocalFile.h"
#include "nsIFile.h"
#include "nsIOutputStream.h"
#include "nsICryptoHash.h"
#include "nsNetCID.h"
#include "nsString.h"
#include "../../../netwerk/base/nsFileStreams.h"
#include "nsToolkitCompsCID.h"
class nsCheckSummedOutputStream : public nsSafeFileOutputStream
{
public:
NS_DECL_ISUPPORTS_INHERITED
// Size of MD5 hash in bytes
static const uint32_t CHECKSUM_SIZE = 16;
nsCheckSummedOutputStream() {}
NS_IMETHOD Finish() override;
NS_IMETHOD Write(const char *buf, uint32_t count, uint32_t *result) override;
NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm, int32_t behaviorFlags) override;
protected:
virtual ~nsCheckSummedOutputStream() { nsSafeFileOutputStream::Close(); }
nsCOMPtr<nsICryptoHash> mHash;
nsAutoCString mCheckSum;
};
// returns a file output stream which can be QI'ed to nsIFileOutputStream.
inline nsresult
NS_NewCheckSummedOutputStream(nsIOutputStream **result,
nsIFile *file,
int32_t ioFlags = -1,
int32_t perm = -1,
int32_t behaviorFlags = 0)
{
nsCOMPtr<nsIFileOutputStream> out = new nsCheckSummedOutputStream();
nsresult rv = out->Init(file, ioFlags, perm, behaviorFlags);
if (NS_SUCCEEDED(rv))
NS_ADDREF(*result = out); // cannot use nsCOMPtr::swap
return rv;
}
#endif
@@ -1,219 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
%{C++
#include "Entries.h"
#include "LookupCache.h"
class nsUrlClassifierLookupResult;
%}
[ptr] native ResultArray(nsTArray<mozilla::safebrowsing::LookupResult>);
[ptr] native CacheCompletionArray(nsTArray<mozilla::safebrowsing::CacheResult>);
[ptr] native PrefixArray(mozilla::safebrowsing::PrefixArray);
interface nsIUrlClassifierHashCompleter;
interface nsIPrincipal;
// Interface for JS function callbacks
[scriptable, function, uuid(4ca27b6b-a674-4b3d-ab30-d21e2da2dffb)]
interface nsIUrlClassifierCallback : nsISupports {
void handleEvent(in ACString value);
};
/**
* The nsIUrlClassifierUpdateObserver interface is implemented by
* clients streaming updates to the url-classifier (usually
* nsUrlClassifierStreamUpdater.
*/
[scriptable, uuid(9fa11561-5816-4e1b-bcc9-b629ca05cce6)]
interface nsIUrlClassifierUpdateObserver : nsISupports {
/**
* The update requested a new URL whose contents should be downloaded
* and sent to the classifier as a new stream.
*
* @param url The url that was requested.
* @param table The table name that this URL's contents will be associated
* with. This should be passed back to beginStream().
*/
void updateUrlRequested(in ACString url,
in ACString table);
/**
* A stream update has completed.
*
* @param status The state of the update process.
* @param delay The amount of time the updater should wait to fetch the
* next URL in ms.
*/
void streamFinished(in nsresult status, in unsigned long delay);
/* The update has encountered an error and should be cancelled */
void updateError(in nsresult error);
/**
* The update has completed successfully.
*
* @param requestedTimeout The number of seconds that the caller should
* wait before trying to update again.
**/
void updateSuccess(in unsigned long requestedTimeout);
};
/**
* This is a proxy class that is instantiated and called from the JS thread.
* It provides async methods for querying and updating the database. As the
* methods complete, they call the callback function.
*/
[scriptable, uuid(3f9e61e5-01bd-45d0-8dd2-f1abcd20dbb7)]
interface nsIUrlClassifierDBService : nsISupports
{
/**
* Looks up a URI in the specified tables.
*
* @param principal: The principal containing the URI to search.
* @param c: The callback will be called with a comma-separated list
* of tables to which the key belongs.
*/
void lookup(in nsIPrincipal principal,
in ACString tables,
in nsIUrlClassifierCallback c);
/**
* Lists the tables along with which chunks are available in each table.
* This list is in the format of the request body:
* tablename;chunkdata\n
* tablename2;chunkdata2\n
*
* For example:
* goog-phish-regexp;a:10,14,30-40s:56,67
* goog-white-regexp;a:1-3,5
*/
void getTables(in nsIUrlClassifierCallback c);
/**
* Set the nsIUrlClassifierCompleter object for a given table. This
* object will be used to request complete versions of partial
* hashes.
*/
void setHashCompleter(in ACString tableName,
in nsIUrlClassifierHashCompleter completer);
////////////////////////////////////////////////////////////////////////////
// Incremental update methods.
//
// An update to the database has the following steps:
//
// 1) The update process is started with beginUpdate(). The client
// passes an nsIUrlClassifierUpdateObserver object which will be
// notified as the update is processed by the dbservice.
// 2) The client sends an initial update stream to the dbservice,
// using beginStream/updateStream/finishStream.
// 3) While reading this initial update stream, the dbservice may
// request additional streams from the client as requested by the
// update stream.
// 4) For each additional update stream, the client feeds the
// contents to the dbservice using beginStream/updateStream/endStream.
// 5) Once all streams have been processed, the client calls
// finishUpdate. When the dbservice has finished processing
// all streams, it will notify the observer that the update process
// is complete.
/**
* Begin an update process. Will throw NS_ERROR_NOT_AVAILABLE if there
* is already an update in progress.
*
* @param updater The update observer tied to this update.
* @param tables A comma-separated list of tables included in this update.
*/
void beginUpdate(in nsIUrlClassifierUpdateObserver updater,
in ACString tables);
/**
* Begin a stream update. This should be called once per url being
* fetched.
*
* @param table The table the contents of this stream will be associated
* with, or empty for the initial stream.
*/
void beginStream(in ACString table);
/**
* Update the table incrementally.
*/
void updateStream(in ACString updateChunk);
// It would be nice to have an updateFromStream method to round out the
// interface, but it's tricky because of XPCOM proxies.
/**
* Finish an individual stream update. Must be called for every
* beginStream() call, before the next beginStream() or finishUpdate().
*
* The update observer's streamFinished will be called once the
* stream has been processed.
*/
void finishStream();
/**
* Finish an incremental update. This will attempt to commit any
* pending changes and resets the update interface.
*
* The update observer's updateSucceeded or updateError methods
* will be called when the update has been processed.
*/
void finishUpdate();
/**
* Cancel an incremental update. This rolls back any pending changes.
* and resets the update interface.
*
* The update observer's updateError method will be called when the
* update has been rolled back.
*/
void cancelUpdate();
/**
* Reset the url-classifier database. This call will delete the existing
* database, emptying all tables. Mostly intended for use in unit tests.
*/
void resetDatabase();
};
/**
* Interface for the actual worker thread. Implementations of this need not
* be thread aware and just work on the database.
*/
[scriptable, uuid(b7b505d0-bfa2-44db-abf8-6e2bfc25bbab)]
interface nsIUrlClassifierDBServiceWorker : nsIUrlClassifierDBService
{
// Open the DB connection
void openDb();
// Provide a way to forcibly close the db connection.
void closeDb();
[noscript]void cacheCompletions(in CacheCompletionArray completions);
[noscript]void cacheMisses(in PrefixArray misses);
};
/**
* This is an internal helper interface for communication between the
* main thread and the dbservice worker thread. It is called for each
* lookup to provide a set of possible results, which the main thread
* may need to expand using an nsIUrlClassifierCompleter.
*/
[uuid(b903dc8f-dff1-42fe-894b-36e7a59bb801)]
interface nsIUrlClassifierLookupCallback : nsISupports
{
/**
* The lookup process is complete.
*
* @param results
* If this parameter is null, there were no results found.
* If not, it contains an array of nsUrlClassifierEntry objects
* with possible matches. The callee is responsible for freeing
* this array.
*/
void lookupComplete(in ResultArray results);
};
@@ -1,65 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
/**
* This interface is implemented by nsIUrlClassifierHashCompleter clients.
*/
[scriptable, uuid(da16de40-df26-414d-bde7-c4faf4504868)]
interface nsIUrlClassifierHashCompleterCallback : nsISupports
{
/**
* A complete hash has been found that matches the partial hash.
* This method may be called 0-n times for a given
* nsIUrlClassifierCompleter::complete() call.
*
* @param hash
* The 128-bit hash that was discovered.
* @param table
* The name of the table that this hash belongs to.
* @param chunkId
* The database chunk that this hash belongs to.
*/
void completion(in ACString hash,
in ACString table,
in uint32_t chunkId);
/**
* The completion is complete. This method is called once per
* nsIUrlClassifierCompleter::complete() call, after all completion()
* calls are finished.
*
* @param status
* NS_OK if the request completed successfully, or an error code.
*/
void completionFinished(in nsresult status);
};
/**
* Clients updating the url-classifier database have the option of sending
* partial (32-bit) hashes of URL fragments to be blacklisted. If the
* url-classifier encounters one of these truncated hashes, it will ask an
* nsIUrlClassifierCompleter instance to asynchronously provide the complete
* hash, along with some associated metadata.
* This is only ever used for testing and should absolutely be deleted (I
* think).
*/
[scriptable, uuid(231fb2ad-ea8a-4e63-a331-eafc3b434811)]
interface nsIUrlClassifierHashCompleter : nsISupports
{
/**
* Request a completed hash from the given gethash url.
*
* @param partialHash
* The 32-bit hash encountered by the url-classifier.
* @param gethashUrl
* The gethash url to use.
* @param callback
* An nsIUrlClassifierCompleterCallback instance.
*/
void complete(in ACString partialHash,
in ACString gethashUrl,
in nsIUrlClassifierHashCompleterCallback callback);
};
@@ -1,29 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "nsIFile.idl"
// Note that the PrefixSet name is historical and we do properly support
// duplicated values, so it's really a Prefix Trie.
// All methods are thread-safe.
[scriptable, uuid(3d8579f0-75fa-4e00-ba41-38661d5b5d17)]
interface nsIUrlClassifierPrefixSet : nsISupports
{
// Initialize the PrefixSet. Give it a name for memory reporting.
void init(in ACString aName);
// Fills the PrefixSet with the given array of prefixes.
// Can send an empty Array to clear the tree.
// Requires array to be sorted.
void setPrefixes([const, array, size_is(aLength)] in unsigned long aPrefixes,
in unsigned long aLength);
void getPrefixes(out unsigned long aCount,
[array, size_is(aCount), retval] out unsigned long aPrefixes);
// Do a lookup in the PrefixSet, return whether the value is present.
boolean contains(in unsigned long aPrefix);
boolean isEmpty();
void loadFromFile(in nsIFile aFile);
void storeToFile(in nsIFile aFile);
};
@@ -1,36 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "nsIUrlClassifierDBService.idl"
/**
* This is a class to manage large table updates from the server. Rather than
* downloading the whole update and then updating the sqlite database, we
* update tables as the data is streaming in.
*/
[scriptable, uuid(e1797597-f4d6-4dd3-a1e1-745ad352cd80)]
interface nsIUrlClassifierStreamUpdater : nsISupports
{
/**
* Try to download updates from updateUrl. If an update is already in
* progress, queues the requested update. This is used in nsIUrlListManager
* as well as in testing.
* @param aRequestTables Comma-separated list of tables included in this
* update.
* @param aRequestBody The body for the request.
* @param aUpdateUrl The plaintext url from which to request updates.
* @param aSuccessCallback Called after a successful update.
* @param aUpdateErrorCallback Called for problems applying the update
* @param aDownloadErrorCallback Called if we get an http error or a
* connection refused error.
*/
boolean downloadUpdates(in ACString aRequestTables,
in ACString aRequestBody,
in ACString aUpdateUrl,
in nsIUrlClassifierCallback aSuccessCallback,
in nsIUrlClassifierCallback aUpdateErrorCallback,
in nsIUrlClassifierCallback aDownloadErrorCallback);
};
@@ -1,31 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "nsIUrlListManager.idl"
// A map that contains a string keys mapped to string values.
[scriptable, uuid(fd1f8334-1859-472d-b01f-4ac6b1121ce4)]
interface nsIUrlClassifierTable : nsISupports
{
/**
* The name used to identify this table
*/
attribute ACString name;
/**
* Set to false if we don't want to update this table.
*/
attribute boolean needsUpdate;
/**
* In the simple case, exists just looks up the string in the
* table and call the callback after the query returns with true or
* false. It's possible that something more complex happens
* (e.g., canonicalize the url).
*/
void exists(in ACString key, in nsIUrlListManagerCallback cb);
};
@@ -1,24 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
/**
* Some utility methods used by the url classifier.
*/
interface nsIURI;
[scriptable, uuid(e4f0e59c-b922-48b0-a7b6-1735c1f96fed)]
interface nsIUrlClassifierUtils : nsISupports
{
/**
* Get the lookup string for a given URI. This normalizes the hostname,
* url-decodes the string, and strips off the protocol.
*
* @param uri URI to get the lookup key for.
*
* @returns String containing the canonicalized URI.
*/
ACString getKeyForURI(in nsIURI uri);
};
@@ -1,65 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIPrincipal;
/**
* Interface for a class that manages updates of the url classifier database.
*/
// Interface for JS function callbacks
[scriptable, function, uuid(fa4caf12-d057-4e7e-81e9-ce066ceee90b)]
interface nsIUrlListManagerCallback : nsISupports {
void handleEvent(in ACString value);
};
[scriptable, uuid(5d5ed98f-72cd-46b6-a9fe-76418adfdfeb)]
interface nsIUrlListManager : nsISupports
{
/**
* Get the gethash url for this table
*/
ACString getGethashUrl(in ACString tableName);
/**
* Add a table to the list of tables we are managing. The name is a
* string of the format provider_name-semantic_type-table_type. For
* @param tableName A string of the format
* provider_name-semantic_type-table_type. For example,
* goog-white-enchash or goog-black-url.
* @param updateUrl The URL from which to fetch updates.
* @param gethashUrl The URL from which to fetch hash completions.
*/
boolean registerTable(in ACString tableName,
in ACString updateUrl,
in ACString gethashUrl);
/**
* Turn on update checking for a table. I.e., during the next server
* check, download updates for this table.
*/
void enableUpdate(in ACString tableName);
/**
* Turn off update checking for a table.
*/
void disableUpdate(in ACString tableName);
/**
* Toggle update checking, if necessary.
*/
void maybeToggleUpdateChecking();
/**
* Lookup a key. Should not raise exceptions. Calls the callback
* function with a comma-separated list of tables to which the key
* belongs.
*/
void safeLookup(in nsIPrincipal key,
in nsIUrlListManagerCallback cb);
};
@@ -1,6 +0,0 @@
component {26a4a019-2827-4a89-a85c-5931a678823a} nsUrlClassifierLib.js
contract @mozilla.org/url-classifier/jslib;1 {26a4a019-2827-4a89-a85c-5931a678823a}
component {ca168834-cc00-48f9-b83c-fd018e58cae3} nsUrlClassifierListManager.js
contract @mozilla.org/url-classifier/listmanager;1 {ca168834-cc00-48f9-b83c-fd018e58cae3}
component {9111de73-9322-4bfc-8b65-2b727f3e6ec8} nsUrlClassifierHashCompleter.js
contract @mozilla.org/url-classifier/hashcompleter;1 {9111de73-9322-4bfc-8b65-2b727f3e6ec8}
File diff suppressed because it is too large Load Diff
@@ -1,238 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsUrlClassifierDBService_h_
#define nsUrlClassifierDBService_h_
#include <nsISupportsUtils.h>
#include "nsID.h"
#include "nsInterfaceHashtable.h"
#include "nsIObserver.h"
#include "nsUrlClassifierPrefixSet.h"
#include "nsIUrlClassifierHashCompleter.h"
#include "nsIUrlListManager.h"
#include "nsIUrlClassifierDBService.h"
#include "nsIURIClassifier.h"
#include "nsToolkitCompsCID.h"
#include "nsICryptoHMAC.h"
#include "mozilla/Attributes.h"
#include "mozilla/Mutex.h"
#include "mozilla/TimeStamp.h"
#include "Entries.h"
#include "LookupCache.h"
// The hash length for a domain key.
#define DOMAIN_LENGTH 4
// The hash length of a partial hash entry.
#define PARTIAL_LENGTH 4
// The hash length of a complete hash entry.
#define COMPLETE_LENGTH 32
using namespace mozilla::safebrowsing;
class nsUrlClassifierDBServiceWorker;
class nsIThread;
class nsIURI;
class UrlClassifierDBServiceWorkerProxy;
namespace mozilla {
namespace safebrowsing {
class Classifier;
class ProtocolParser;
class TableUpdate;
}
}
// This is a proxy class that just creates a background thread and delagates
// calls to the background thread.
class nsUrlClassifierDBService final : public nsIUrlClassifierDBService,
public nsIURIClassifier,
public nsIObserver
{
public:
// This is thread safe. It throws an exception if the thread is busy.
nsUrlClassifierDBService();
nsresult Init();
static nsUrlClassifierDBService* GetInstance(nsresult *result);
NS_DECLARE_STATIC_IID_ACCESSOR(NS_URLCLASSIFIERDBSERVICE_CID)
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERDBSERVICE
NS_DECL_NSIURICLASSIFIER
NS_DECL_NSIOBSERVER
bool GetCompleter(const nsACString& tableName,
nsIUrlClassifierHashCompleter** completer);
nsresult CacheCompletions(mozilla::safebrowsing::CacheResultArray *results);
nsresult CacheMisses(mozilla::safebrowsing::PrefixArray *results);
static nsIThread* BackgroundThread();
private:
// No subclassing
~nsUrlClassifierDBService();
// Disallow copy constructor
nsUrlClassifierDBService(nsUrlClassifierDBService&);
nsresult LookupURI(nsIPrincipal* aPrincipal,
const nsACString& tables,
nsIUrlClassifierCallback* c,
bool forceCheck, bool *didCheck);
// Close db connection and join the background thread if it exists.
nsresult Shutdown();
// Check if the key is on a known-clean host.
nsresult CheckClean(const nsACString &lookupKey,
bool *clean);
// Read everything into mGethashTables and mDisallowCompletionTables
nsresult ReadTablesFromPrefs();
// Build a comma-separated list of tables to check
void BuildTables(bool trackingProtectionEnabled, nsCString& tables);
nsRefPtr<nsUrlClassifierDBServiceWorker> mWorker;
nsRefPtr<UrlClassifierDBServiceWorkerProxy> mWorkerProxy;
nsInterfaceHashtable<nsCStringHashKey, nsIUrlClassifierHashCompleter> mCompleters;
// TRUE if the nsURIClassifier implementation should check for malware
// uris on document loads.
bool mCheckMalware;
// TRUE if the nsURIClassifier implementation should check for phishing
// uris on document loads.
bool mCheckPhishing;
// TRUE if the nsURIClassifier implementation should check for tracking
// uris on document loads.
bool mCheckTracking;
// TRUE if a BeginUpdate() has been called without an accompanying
// CancelUpdate()/FinishUpdate(). This is used to prevent competing
// updates, not to determine whether an update is still being
// processed.
bool mInUpdate;
// The list of tables that can use the default hash completer object.
nsTArray<nsCString> mGethashTables;
// The list of tables that should never be hash completed.
nsTArray<nsCString> mDisallowCompletionsTables;
// Thread that we do the updates on.
static nsIThread* gDbBackgroundThread;
};
class nsUrlClassifierDBServiceWorker final :
public nsIUrlClassifierDBServiceWorker
{
public:
nsUrlClassifierDBServiceWorker();
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERDBSERVICE
NS_DECL_NSIURLCLASSIFIERDBSERVICEWORKER
nsresult Init(uint32_t aGethashNoise, nsCOMPtr<nsIFile> aCacheDir);
// Queue a lookup for the worker to perform, called in the main thread.
// tables is a comma-separated list of tables to query
nsresult QueueLookup(const nsACString& lookupKey,
const nsACString& tables,
nsIUrlClassifierLookupCallback* callback);
// Handle any queued-up lookups. We call this function during long-running
// update operations to prevent lookups from blocking for too long.
nsresult HandlePendingLookups();
// Perform a blocking classifier lookup for a given url. Can be called on
// either the main thread or the worker thread.
nsresult DoLocalLookup(const nsACString& spec,
const nsACString& tables,
LookupResultArray* results);
private:
// No subclassing
~nsUrlClassifierDBServiceWorker();
// Disallow copy constructor
nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&);
// Applies the current transaction and resets the update/working times.
nsresult ApplyUpdate();
// Reset the in-progress update stream
void ResetStream();
// Reset the in-progress update
void ResetUpdate();
// Perform a classifier lookup for a given url.
nsresult DoLookup(const nsACString& spec,
const nsACString& tables,
nsIUrlClassifierLookupCallback* c);
nsresult AddNoise(const Prefix aPrefix,
const nsCString tableName,
uint32_t aCount,
LookupResultArray& results);
// Can only be used on the background thread
nsCOMPtr<nsICryptoHash> mCryptoHash;
nsAutoPtr<mozilla::safebrowsing::Classifier> mClassifier;
// The class that actually parses the update chunks.
nsAutoPtr<ProtocolParser> mProtocolParser;
// Directory where to store the SB databases.
nsCOMPtr<nsIFile> mCacheDir;
// XXX: maybe an array of autoptrs. Or maybe a class specifically
// storing a series of updates.
nsTArray<mozilla::safebrowsing::TableUpdate*> mTableUpdates;
int32_t mUpdateWait;
// Entries that cannot be completed. We expect them to die at
// the next update
PrefixArray mMissCache;
nsresult mUpdateStatus;
nsTArray<nsCString> mUpdateTables;
nsCOMPtr<nsIUrlClassifierUpdateObserver> mUpdateObserver;
bool mInStream;
// The number of noise entries to add to the set of lookup results.
uint32_t mGethashNoise;
// Pending lookups are stored in a queue for processing. The queue
// is protected by mPendingLookupLock.
mozilla::Mutex mPendingLookupLock;
class PendingLookup {
public:
mozilla::TimeStamp mStartTime;
nsCString mKey;
nsCString mTables;
nsCOMPtr<nsIUrlClassifierLookupCallback> mCallback;
};
// list of pending lookups
nsTArray<PendingLookup> mPendingLookups;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsUrlClassifierDBService, NS_URLCLASSIFIERDBSERVICE_CID)
#endif // nsUrlClassifierDBService_h_
@@ -1,466 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
// COMPLETE_LENGTH and PARTIAL_LENGTH copied from nsUrlClassifierDBService.h,
// they correspond to the length, in bytes, of a hash prefix and the total
// hash.
const COMPLETE_LENGTH = 32;
const PARTIAL_LENGTH = 4;
// These backoff related constants are taken from v2 of the Google Safe Browsing
// API. All times are in milliseconds.
// BACKOFF_ERRORS: the number of errors incurred until we start to back off.
// BACKOFF_INTERVAL: the initial time to wait once we start backing
// off.
// BACKOFF_MAX: as the backoff time doubles after each failure, this is a
// ceiling on the time to wait.
const BACKOFF_ERRORS = 2;
const BACKOFF_INTERVAL = 30 * 60 * 1000;
const BACKOFF_MAX = 8 * 60 * 60 * 1000;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function HashCompleter() {
// The current HashCompleterRequest in flight. Once it is started, it is set
// to null. It may be used by multiple calls to |complete| in succession to
// avoid creating multiple requests to the same gethash URL.
this._currentRequest = null;
// A map of gethashUrls to HashCompleterRequests that haven't yet begun.
this._pendingRequests = {};
// A map of gethash URLs to RequestBackoff objects.
this._backoffs = {};
// Whether we have been informed of a shutdown by the xpcom-shutdown event.
this._shuttingDown = false;
Services.obs.addObserver(this, "xpcom-shutdown", true);
}
HashCompleter.prototype = {
classID: Components.ID("{9111de73-9322-4bfc-8b65-2b727f3e6ec8}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUrlClassifierHashCompleter,
Ci.nsIRunnable,
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
Ci.nsITimerCallback,
Ci.nsISupports]),
// This is mainly how the HashCompleter interacts with other components.
// Even though it only takes one partial hash and callback, subsequent
// calls are made into the same HTTP request by using a thread dispatch.
complete: function HC_complete(aPartialHash, aGethashUrl, aCallback) {
if (!aGethashUrl) {
throw Cr.NS_ERROR_NOT_INITIALIZED;
}
if (!this._currentRequest) {
this._currentRequest = new HashCompleterRequest(this, aGethashUrl);
}
if (this._currentRequest.gethashUrl == aGethashUrl) {
this._currentRequest.add(aPartialHash, aCallback);
} else {
if (!this._pendingRequests[aGethashUrl]) {
this._pendingRequests[aGethashUrl] =
new HashCompleterRequest(this, aGethashUrl);
}
this._pendingRequests[aGethashUrl].add(aPartialHash, aCallback);
}
if (!this._backoffs[aGethashUrl]) {
// Initialize request backoffs separately, since requests are deleted
// after they are dispatched.
var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
.getService().wrappedJSObject;
this._backoffs[aGethashUrl] = new jslib.RequestBackoff(
BACKOFF_ERRORS /* max errors */,
60*1000 /* retry interval, 1 min */,
10 /* keep track of max requests */,
0 /* don't throttle on successful requests per time period */,
BACKOFF_INTERVAL /* backoff interval, 60 min */,
BACKOFF_MAX /* max backoff, 8hr */);
}
// Start off this request. Without dispatching to a thread, every call to
// complete makes an individual HTTP request.
Services.tm.currentThread.dispatch(this, Ci.nsIThread.DISPATCH_NORMAL);
},
// This is called after several calls to |complete|, or after the
// currentRequest has finished. It starts off the HTTP request by making a
// |begin| call to the HashCompleterRequest.
run: function() {
// Clear everything on shutdown
if (this._shuttingDown) {
this._currentRequest = null;
this._pendingRequests = null;
for (var url in this._backoffs) {
this._backoffs[url] = null;
}
throw Cr.NS_ERROR_NOT_INITIALIZED;
}
// If we don't have an in-flight request, make one
let pendingUrls = Object.keys(this._pendingRequests);
if (!this._currentRequest && (pendingUrls.length > 0)) {
let nextUrl = pendingUrls[0];
this._currentRequest = this._pendingRequests[nextUrl];
delete this._pendingRequests[nextUrl];
}
if (this._currentRequest) {
try {
this._currentRequest.begin();
} finally {
// If |begin| fails, we should get rid of our request.
this._currentRequest = null;
}
}
},
// Pass the server response status to the RequestBackoff for the given
// gethashUrl and fetch the next pending request, if there is one.
finishRequest: function(url, aStatus) {
this._backoffs[url].noteServerResponse(aStatus);
Services.tm.currentThread.dispatch(this, Ci.nsIThread.DISPATCH_NORMAL);
},
// Returns true if we can make a request from the given url, false otherwise.
canMakeRequest: function(aGethashUrl) {
return this._backoffs[aGethashUrl].canMakeRequest();
},
// Notifies the RequestBackoff of a new request so we can throttle based on
// max requests/time period. This must be called before a channel is opened,
// and finishRequest must be called once the response is received.
noteRequest: function(aGethashUrl) {
return this._backoffs[aGethashUrl].noteRequest();
},
observe: function HC_observe(aSubject, aTopic, aData) {
if (aTopic == "xpcom-shutdown") {
this._shuttingDown = true;
}
},
};
function HashCompleterRequest(aCompleter, aGethashUrl) {
// HashCompleter object that created this HashCompleterRequest.
this._completer = aCompleter;
// The internal set of hashes and callbacks that this request corresponds to.
this._requests = [];
// nsIChannel that the hash completion query is transmitted over.
this._channel = null;
// Response body of hash completion. Created in onDataAvailable.
this._response = "";
// Whether we have been informed of a shutdown by the xpcom-shutdown event.
this._shuttingDown = false;
this.gethashUrl = aGethashUrl;
}
HashCompleterRequest.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
Ci.nsIStreamListener,
Ci.nsIObserver,
Ci.nsISupports]),
// This is called by the HashCompleter to add a hash and callback to the
// HashCompleterRequest. It must be called before calling |begin|.
add: function HCR_add(aPartialHash, aCallback) {
this._requests.push({
partialHash: aPartialHash,
callback: aCallback,
responses: []
});
},
// This initiates the HTTP request. It can fail due to backoff timings and
// will notify all callbacks as necessary. We notify the backoff object on
// begin.
begin: function HCR_begin() {
if (!this._completer.canMakeRequest(this.gethashUrl)) {
dump("hashcompleter: Can't make request to " + this.gethashUrl + "\n");
this.notifyFailure(Cr.NS_ERROR_ABORT);
return;
}
Services.obs.addObserver(this, "xpcom-shutdown", false);
try {
this.openChannel();
// Notify the RequestBackoff if opening the channel succeeded. At this
// point, finishRequest must be called.
this._completer.noteRequest(this.gethashUrl);
}
catch (err) {
this.notifyFailure(err);
throw err;
}
},
notify: function HCR_notify() {
// If we haven't gotten onStopRequest, just cancel. This will call us
// with onStopRequest since we implement nsIStreamListener on the
// channel.
if (this._channel && this._channel.isPending()) {
dump("hashcompleter: cancelling request to " + this.gethashUrl + "\n");
this._channel.cancel(Cr.NS_BINDING_ABORTED);
}
},
// Creates an nsIChannel for the request and fills the body.
openChannel: function HCR_openChannel() {
let loadFlags = Ci.nsIChannel.INHIBIT_CACHING |
Ci.nsIChannel.LOAD_BYPASS_CACHE;
let uri = Services.io.newURI(this.gethashUrl, null, null);
let channel = Services.io.newChannelFromURI2(uri,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_OTHER);
channel.loadFlags = loadFlags;
// Disable keepalive.
let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
httpChannel.setRequestHeader("Connection", "close", false);
this._channel = channel;
let body = this.buildRequest();
this.addRequestBody(body);
// Set a timer that cancels the channel after timeout_ms in case we
// don't get a gethash response.
this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
// Ask the timer to use nsITimerCallback (.notify()) when ready
let timeout = Services.prefs.getIntPref(
"urlclassifier.gethash.timeout_ms");
this.timer_.initWithCallback(this, timeout, this.timer_.TYPE_ONE_SHOT);
channel.asyncOpen(this, null);
},
// Returns a string for the request body based on the contents of
// this._requests.
buildRequest: function HCR_buildRequest() {
// Sometimes duplicate entries are sent to HashCompleter but we do not need
// to propagate these to the server. (bug 633644)
let prefixes = [];
for (let i = 0; i < this._requests.length; i++) {
let request = this._requests[i];
if (prefixes.indexOf(request.partialHash) == -1) {
prefixes.push(request.partialHash);
}
}
// Randomize the order to obscure the original request from noise
// unbiased Fisher-Yates shuffle
let i = prefixes.length;
while (i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = prefixes[i];
prefixes[i] = prefixes[j];
prefixes[j] = temp;
}
let body;
body = PARTIAL_LENGTH + ":" + (PARTIAL_LENGTH * prefixes.length) +
"\n" + prefixes.join("");
return body;
},
// Sets the request body of this._channel.
addRequestBody: function HCR_addRequestBody(aBody) {
let inputStream = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
inputStream.setData(aBody, aBody.length);
let uploadChannel = this._channel.QueryInterface(Ci.nsIUploadChannel);
uploadChannel.setUploadStream(inputStream, "text/plain", -1);
let httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel);
httpChannel.requestMethod = "POST";
},
// Parses the response body and eventually adds items to the |responses| array
// for elements of |this._requests|.
handleResponse: function HCR_handleResponse() {
if (this._response == "") {
return;
}
let start = 0;
let length = this._response.length;
while (start != length) {
start = this.handleTable(start);
}
},
// This parses a table entry in the response body and calls |handleItem|
// for complete hash in the table entry.
handleTable: function HCR_handleTable(aStart) {
let body = this._response.substring(aStart);
// deal with new line indexes as there could be
// new line characters in the data parts.
let newlineIndex = body.indexOf("\n");
if (newlineIndex == -1) {
throw errorWithStack();
}
let header = body.substring(0, newlineIndex);
let entries = header.split(":");
if (entries.length != 3) {
throw errorWithStack();
}
let list = entries[0];
let addChunk = parseInt(entries[1]);
let dataLength = parseInt(entries[2]);
if (dataLength % COMPLETE_LENGTH != 0 ||
dataLength == 0 ||
dataLength > body.length - (newlineIndex + 1)) {
throw errorWithStack();
}
let data = body.substr(newlineIndex + 1, dataLength);
for (let i = 0; i < (dataLength / COMPLETE_LENGTH); i++) {
this.handleItem(data.substr(i * COMPLETE_LENGTH, COMPLETE_LENGTH), list,
addChunk);
}
return aStart + newlineIndex + 1 + dataLength;
},
// This adds a complete hash to any entry in |this._requests| that matches
// the hash.
handleItem: function HCR_handleItem(aData, aTableName, aChunkId) {
for (let i = 0; i < this._requests.length; i++) {
let request = this._requests[i];
if (aData.substring(0,4) == request.partialHash) {
request.responses.push({
completeHash: aData,
tableName: aTableName,
chunkId: aChunkId,
});
}
}
},
// notifySuccess and notifyFailure are used to alert the callbacks with
// results. notifySuccess makes |completion| and |completionFinished| calls
// while notifyFailure only makes a |completionFinished| call with the error
// code.
notifySuccess: function HCR_notifySuccess() {
for (let i = 0; i < this._requests.length; i++) {
let request = this._requests[i];
for (let j = 0; j < request.responses.length; j++) {
let response = request.responses[j];
request.callback.completion(response.completeHash, response.tableName,
response.chunkId);
}
request.callback.completionFinished(Cr.NS_OK);
}
},
notifyFailure: function HCR_notifyFailure(aStatus) {
dump("hashcompleter: notifying failure\n");
for (let i = 0; i < this._requests.length; i++) {
let request = this._requests[i];
request.callback.completionFinished(aStatus);
}
},
onDataAvailable: function HCR_onDataAvailable(aRequest, aContext,
aInputStream, aOffset, aCount) {
let sis = Cc["@mozilla.org/scriptableinputstream;1"].
createInstance(Ci.nsIScriptableInputStream);
sis.init(aInputStream);
this._response += sis.readBytes(aCount);
},
onStartRequest: function HCR_onStartRequest(aRequest, aContext) {
// At this point no data is available for us and we have no reason to
// terminate the connection, so we do nothing until |onStopRequest|.
},
onStopRequest: function HCR_onStopRequest(aRequest, aContext, aStatusCode) {
Services.obs.removeObserver(this, "xpcom-shutdown");
if (this._shuttingDown) {
throw Cr.NS_ERROR_ABORT;
}
// Default HTTP status to service unavailable, in case we can't retrieve
// the true status from the channel.
let httpStatus = 503;
if (Components.isSuccessCode(aStatusCode)) {
let channel = aRequest.QueryInterface(Ci.nsIHttpChannel);
let success = channel.requestSucceeded;
httpStatus = channel.responseStatus;
if (!success) {
aStatusCode = Cr.NS_ERROR_ABORT;
}
}
let success = Components.isSuccessCode(aStatusCode);
// Notify the RequestBackoff once a response is received.
this._completer.finishRequest(this.gethashUrl, httpStatus);
if (success) {
try {
this.handleResponse();
}
catch (err) {
dump(err.stack);
aStatusCode = err.value;
success = false;
}
}
if (success) {
this.notifySuccess();
} else {
this.notifyFailure(aStatusCode);
}
},
observe: function HCR_observe(aSubject, aTopic, aData) {
if (aTopic != "xpcom-shutdown") {
return;
}
this._shuttingDown = true;
if (this._channel) {
this._channel.cancel(Cr.NS_ERROR_ABORT);
}
},
};
// Converts a URL safe base64 string to a normal base64 string. Will not change
// normal base64 strings. This is modelled after the same function in
// nsUrlClassifierUtils.h.
function unUrlsafeBase64(aStr) {
return !aStr ? "" : aStr.replace(/-/g, "+")
.replace(/_/g, "/");
}
function errorWithStack() {
let err = new Error();
err.value = Cr.NS_ERROR_FAILURE;
return err;
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HashCompleter]);
@@ -1,35 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// We wastefully reload the same JS files across components. This puts all
// the common JS files used by safebrowsing and url-classifier into a
// single component.
const Cc = Components.classes;
const Ci = Components.interfaces;
const G_GDEBUG = false;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
#include ./content/moz/lang.js
#include ./content/moz/preferences.js
#include ./content/moz/debug.js
#include ./content/moz/alarm.js
#include ./content/moz/cryptohasher.js
#include ./content/moz/observer.js
#include ./content/moz/protocol4.js
#include ./content/request-backoff.js
#include ./content/xml-fetcher.js
// Expose this whole component.
var lib = this;
function UrlClassifierLib() {
this.wrappedJSObject = lib;
}
UrlClassifierLib.prototype.classID = Components.ID("{26a4a019-2827-4a89-a85c-5931a678823a}");
UrlClassifierLib.prototype.QueryInterface = XPCOMUtils.generateQI([]);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UrlClassifierLib]);
@@ -1,53 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
const Cc = Components.classes;
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
#include ./content/listmanager.js
var modScope = this;
function Init() {
// Pull the library in.
var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
.getService().wrappedJSObject;
Function.prototype.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
},
modScope.G_Preferences = jslib.G_Preferences;
modScope.G_PreferenceObserver = jslib.G_PreferenceObserver;
modScope.G_ObserverServiceObserver = jslib.G_ObserverServiceObserver;
modScope.G_Debug = jslib.G_Debug;
modScope.G_Assert = jslib.G_Assert;
modScope.G_debugService = jslib.G_debugService;
modScope.G_Alarm = jslib.G_Alarm;
modScope.BindToObject = jslib.BindToObject;
modScope.PROT_XMLFetcher = jslib.PROT_XMLFetcher;
modScope.RequestBackoff = jslib.RequestBackoff;
// We only need to call Init once.
modScope.Init = function() {};
}
function RegistrationData()
{
}
RegistrationData.prototype = {
classID: Components.ID("{ca168834-cc00-48f9-b83c-fd018e58cae3}"),
_xpcom_factory: {
createInstance: function(outer, iid) {
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
Init();
return (new PROT_ListManager()).QueryInterface(iid);
}
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RegistrationData]);
@@ -1,437 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsPrintfCString.h"
#include "nsTArray.h"
#include "nsString.h"
#include "nsUrlClassifierPrefixSet.h"
#include "nsIUrlClassifierPrefixSet.h"
#include "nsIFile.h"
#include "nsToolkitCompsCID.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Telemetry.h"
#include "mozilla/FileUtils.h"
#include "prlog.h"
using namespace mozilla;
// NSPR_LOG_MODULES=UrlClassifierPrefixSet:5
#if defined(PR_LOGGING)
static const PRLogModuleInfo *gUrlClassifierPrefixSetLog = nullptr;
#define LOG(args) PR_LOG(gUrlClassifierPrefixSetLog, PR_LOG_DEBUG, args)
#define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierPrefixSetLog, 4)
#else
#define LOG(args)
#define LOG_ENABLED() (false)
#endif
NS_IMPL_ISUPPORTS(
nsUrlClassifierPrefixSet, nsIUrlClassifierPrefixSet, nsIMemoryReporter)
MOZ_DEFINE_MALLOC_SIZE_OF(UrlClassifierMallocSizeOf)
nsUrlClassifierPrefixSet::nsUrlClassifierPrefixSet()
: mTotalPrefixes(0)
, mMemoryInUse(0)
, mMemoryReportPath()
{
#if defined(PR_LOGGING)
if (!gUrlClassifierPrefixSetLog)
gUrlClassifierPrefixSetLog = PR_NewLogModule("UrlClassifierPrefixSet");
#endif
}
NS_IMETHODIMP
nsUrlClassifierPrefixSet::Init(const nsACString& aName)
{
mMemoryReportPath =
nsPrintfCString(
"explicit/storage/prefix-set/%s",
(!aName.IsEmpty() ? PromiseFlatCString(aName).get() : "?!")
);
RegisterWeakMemoryReporter(this);
return NS_OK;
}
nsUrlClassifierPrefixSet::~nsUrlClassifierPrefixSet()
{
UnregisterWeakMemoryReporter(this);
}
NS_IMETHODIMP
nsUrlClassifierPrefixSet::SetPrefixes(const uint32_t* aArray, uint32_t aLength)
{
nsresult rv = NS_OK;
if (aLength <= 0) {
if (mIndexPrefixes.Length() > 0) {
LOG(("Clearing PrefixSet"));
mIndexDeltas.Clear();
mIndexPrefixes.Clear();
mTotalPrefixes = 0;
}
} else {
rv = MakePrefixSet(aArray, aLength);
}
mMemoryInUse = SizeOfIncludingThis(UrlClassifierMallocSizeOf);
return rv;
}
nsresult
nsUrlClassifierPrefixSet::MakePrefixSet(const uint32_t* aPrefixes, uint32_t aLength)
{
if (aLength == 0) {
return NS_OK;
}
#ifdef DEBUG
for (uint32_t i = 1; i < aLength; i++) {
MOZ_ASSERT(aPrefixes[i] >= aPrefixes[i-1]);
}
#endif
mIndexPrefixes.Clear();
mIndexDeltas.Clear();
mTotalPrefixes = aLength;
mIndexPrefixes.AppendElement(aPrefixes[0]);
mIndexDeltas.AppendElement();
uint32_t numOfDeltas = 0;
uint32_t totalDeltas = 0;
uint32_t currentItem = aPrefixes[0];
for (uint32_t i = 1; i < aLength; i++) {
if ((numOfDeltas >= DELTAS_LIMIT) ||
(aPrefixes[i] - currentItem >= MAX_INDEX_DIFF)) {
mIndexDeltas.AppendElement();
mIndexDeltas[mIndexDeltas.Length() - 1].Compact();
mIndexPrefixes.AppendElement(aPrefixes[i]);
numOfDeltas = 0;
} else {
uint16_t delta = aPrefixes[i] - currentItem;
mIndexDeltas[mIndexDeltas.Length() - 1].AppendElement(delta);
numOfDeltas++;
totalDeltas++;
}
currentItem = aPrefixes[i];
}
mIndexPrefixes.Compact();
mIndexDeltas.Compact();
LOG(("Total number of indices: %d", aLength));
LOG(("Total number of deltas: %d", totalDeltas));
LOG(("Total number of delta chunks: %d", mIndexDeltas.Length()));
return NS_OK;
}
nsresult
nsUrlClassifierPrefixSet::GetPrefixesNative(FallibleTArray<uint32_t>& outArray)
{
if (!outArray.SetLength(mTotalPrefixes)) {
return NS_ERROR_OUT_OF_MEMORY;
}
uint32_t prefixIdxLength = mIndexPrefixes.Length();
uint32_t prefixCnt = 0;
for (uint32_t i = 0; i < prefixIdxLength; i++) {
uint32_t prefix = mIndexPrefixes[i];
outArray[prefixCnt++] = prefix;
for (uint32_t j = 0; j < mIndexDeltas[i].Length(); j++) {
prefix += mIndexDeltas[i][j];
outArray[prefixCnt++] = prefix;
}
}
NS_ASSERTION(mTotalPrefixes == prefixCnt, "Lengths are inconsistent");
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierPrefixSet::GetPrefixes(uint32_t* aCount,
uint32_t** aPrefixes)
{
NS_ENSURE_ARG_POINTER(aCount);
*aCount = 0;
NS_ENSURE_ARG_POINTER(aPrefixes);
*aPrefixes = nullptr;
FallibleTArray<uint32_t> prefixes;
nsresult rv = GetPrefixesNative(prefixes);
if (NS_FAILED(rv)) {
return rv;
}
uint64_t itemCount = prefixes.Length();
uint32_t* prefixArray = static_cast<uint32_t*>(nsMemory::Alloc(itemCount * sizeof(uint32_t)));
NS_ENSURE_TRUE(prefixArray, NS_ERROR_OUT_OF_MEMORY);
memcpy(prefixArray, prefixes.Elements(), sizeof(uint32_t) * itemCount);
*aCount = itemCount;
*aPrefixes = prefixArray;
return NS_OK;
}
uint32_t nsUrlClassifierPrefixSet::BinSearch(uint32_t start,
uint32_t end,
uint32_t target)
{
while (start != end && end >= start) {
uint32_t i = start + ((end - start) >> 1);
uint32_t value = mIndexPrefixes[i];
if (value < target) {
start = i + 1;
} else if (value > target) {
end = i - 1;
} else {
return i;
}
}
return end;
}
NS_IMETHODIMP
nsUrlClassifierPrefixSet::Contains(uint32_t aPrefix, bool* aFound)
{
*aFound = false;
if (mIndexPrefixes.Length() == 0) {
return NS_OK;
}
uint32_t target = aPrefix;
// We want to do a "Price is Right" binary search, that is, we want to find
// the index of the value either equal to the target or the closest value
// that is less than the target.
//
if (target < mIndexPrefixes[0]) {
return NS_OK;
}
// |binsearch| does not necessarily return the correct index (when the
// target is not found) but rather it returns an index at least one away
// from the correct index.
// Because of this, we need to check if the target lies before the beginning
// of the indices.
uint32_t i = BinSearch(0, mIndexPrefixes.Length() - 1, target);
if (mIndexPrefixes[i] > target && i > 0) {
i--;
}
// Now search through the deltas for the target.
uint32_t diff = target - mIndexPrefixes[i];
uint32_t deltaSize = mIndexDeltas[i].Length();
uint32_t deltaIndex = 0;
while (diff > 0 && deltaIndex < deltaSize) {
diff -= mIndexDeltas[i][deltaIndex];
deltaIndex++;
}
if (diff == 0) {
*aFound = true;
}
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierPrefixSet::CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize)
{
return aHandleReport->Callback(
EmptyCString(), mMemoryReportPath, KIND_HEAP, UNITS_BYTES,
mMemoryInUse,
NS_LITERAL_CSTRING("Memory used by the prefix set for a URL classifier."),
aData);
}
size_t
nsUrlClassifierPrefixSet::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
{
size_t n = 0;
n += aMallocSizeOf(this);
n += mIndexDeltas.SizeOfExcludingThis(aMallocSizeOf);
for (uint32_t i = 0; i < mIndexDeltas.Length(); i++) {
n += mIndexDeltas[i].SizeOfExcludingThis(aMallocSizeOf);
}
n += mIndexPrefixes.SizeOfExcludingThis(aMallocSizeOf);
return n;
}
NS_IMETHODIMP
nsUrlClassifierPrefixSet::IsEmpty(bool * aEmpty)
{
*aEmpty = (mIndexPrefixes.Length() == 0);
return NS_OK;
}
nsresult
nsUrlClassifierPrefixSet::LoadFromFd(AutoFDClose& fileFd)
{
uint32_t magic;
int32_t read;
read = PR_Read(fileFd, &magic, sizeof(uint32_t));
NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FAILURE);
if (magic == PREFIXSET_VERSION_MAGIC) {
uint32_t indexSize;
uint32_t deltaSize;
read = PR_Read(fileFd, &indexSize, sizeof(uint32_t));
NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FILE_CORRUPTED);
read = PR_Read(fileFd, &deltaSize, sizeof(uint32_t));
NS_ENSURE_TRUE(read == sizeof(uint32_t), NS_ERROR_FILE_CORRUPTED);
if (indexSize == 0) {
LOG(("stored PrefixSet is empty!"));
return NS_OK;
}
if (deltaSize > (indexSize * DELTAS_LIMIT)) {
return NS_ERROR_FILE_CORRUPTED;
}
nsTArray<uint32_t> indexStarts;
indexStarts.SetLength(indexSize);
mIndexPrefixes.SetLength(indexSize);
mIndexDeltas.SetLength(indexSize);
mTotalPrefixes = indexSize;
int32_t toRead = indexSize*sizeof(uint32_t);
read = PR_Read(fileFd, mIndexPrefixes.Elements(), toRead);
NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED);
read = PR_Read(fileFd, indexStarts.Elements(), toRead);
NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED);
if (indexSize != 0 && indexStarts[0] != 0) {
return NS_ERROR_FILE_CORRUPTED;
}
for (uint32_t i = 0; i < indexSize; i++) {
uint32_t numInDelta = i == indexSize - 1 ? deltaSize - indexStarts[i]
: indexStarts[i + 1] - indexStarts[i];
if (numInDelta > 0) {
mIndexDeltas[i].SetLength(numInDelta);
mTotalPrefixes += numInDelta;
toRead = numInDelta * sizeof(uint16_t);
read = PR_Read(fileFd, mIndexDeltas[i].Elements(), toRead);
NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED);
}
}
} else {
LOG(("Version magic mismatch, not loading"));
return NS_ERROR_FILE_CORRUPTED;
}
MOZ_ASSERT(mIndexPrefixes.Length() == mIndexDeltas.Length());
LOG(("Loading PrefixSet successful"));
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierPrefixSet::LoadFromFile(nsIFile* aFile)
{
Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_PS_FILELOAD_TIME> timer;
nsresult rv;
AutoFDClose fileFd;
rv = aFile->OpenNSPRFileDesc(PR_RDONLY | nsIFile::OS_READAHEAD,
0, &fileFd.rwget());
if (!NS_FAILED(rv)) {
rv = LoadFromFd(fileFd);
mMemoryInUse = SizeOfIncludingThis(UrlClassifierMallocSizeOf);
}
return rv;
}
nsresult
nsUrlClassifierPrefixSet::StoreToFd(AutoFDClose& fileFd)
{
{
Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_PS_FALLOCATE_TIME> timer;
int64_t size = 4 * sizeof(uint32_t);
uint32_t deltas = mTotalPrefixes - mIndexPrefixes.Length();
size += 2 * mIndexPrefixes.Length() * sizeof(uint32_t);
size += deltas * sizeof(uint16_t);
mozilla::fallocate(fileFd, size);
}
int32_t written;
int32_t writelen = sizeof(uint32_t);
uint32_t magic = PREFIXSET_VERSION_MAGIC;
written = PR_Write(fileFd, &magic, writelen);
NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE);
uint32_t indexSize = mIndexPrefixes.Length();
uint32_t indexDeltaSize = mIndexDeltas.Length();
uint32_t totalDeltas = 0;
// Store the shape of mIndexDeltas by noting at which "count" of total
// indexes a new subarray starts. This is slightly cumbersome but keeps
// file format compatibility.
// If we ever update the format, we can gain space by storing the delta
// subarray sizes, which fit in bytes.
nsTArray<uint32_t> indexStarts;
indexStarts.AppendElement(0);
for (uint32_t i = 0; i < indexDeltaSize; i++) {
uint32_t deltaLength = mIndexDeltas[i].Length();
totalDeltas += deltaLength;
indexStarts.AppendElement(totalDeltas);
}
written = PR_Write(fileFd, &indexSize, writelen);
NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE);
written = PR_Write(fileFd, &totalDeltas, writelen);
NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE);
writelen = indexSize * sizeof(uint32_t);
written = PR_Write(fileFd, mIndexPrefixes.Elements(), writelen);
NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE);
written = PR_Write(fileFd, indexStarts.Elements(), writelen);
NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE);
if (totalDeltas > 0) {
for (uint32_t i = 0; i < indexDeltaSize; i++) {
writelen = mIndexDeltas[i].Length() * sizeof(uint16_t);
written = PR_Write(fileFd, mIndexDeltas[i].Elements(), writelen);
NS_ENSURE_TRUE(written == writelen, NS_ERROR_FAILURE);
}
}
LOG(("Saving PrefixSet successful\n"));
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierPrefixSet::StoreToFile(nsIFile* aFile)
{
AutoFDClose fileFd;
nsresult rv = aFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE,
0644, &fileFd.rwget());
NS_ENSURE_SUCCESS(rv, rv);
return StoreToFd(fileFd);
}
@@ -1,75 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsUrlClassifierPrefixSet_h_
#define nsUrlClassifierPrefixSet_h_
#include "nsISupportsUtils.h"
#include "nsID.h"
#include "nsIFile.h"
#include "nsIMemoryReporter.h"
#include "nsIMutableArray.h"
#include "nsIUrlClassifierPrefixSet.h"
#include "nsTArray.h"
#include "nsToolkitCompsCID.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/FileUtils.h"
#include "mozilla/Atomics.h"
class nsUrlClassifierPrefixSet final
: public nsIUrlClassifierPrefixSet
, public nsIMemoryReporter
{
public:
nsUrlClassifierPrefixSet();
NS_IMETHOD Init(const nsACString& aName) override;
NS_IMETHOD SetPrefixes(const uint32_t* aArray, uint32_t aLength) override;
NS_IMETHOD GetPrefixes(uint32_t* aCount, uint32_t** aPrefixes) override;
NS_IMETHOD Contains(uint32_t aPrefix, bool* aFound) override;
NS_IMETHOD IsEmpty(bool* aEmpty) override;
NS_IMETHOD LoadFromFile(nsIFile* aFile) override;
NS_IMETHOD StoreToFile(nsIFile* aFile) override;
nsresult GetPrefixesNative(FallibleTArray<uint32_t>& outArray);
size_t SizeInMemory() { return mMemoryInUse; };
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIMEMORYREPORTER
protected:
virtual ~nsUrlClassifierPrefixSet();
static const uint32_t DELTAS_LIMIT = 120;
static const uint32_t MAX_INDEX_DIFF = (1 << 16);
static const uint32_t PREFIXSET_VERSION_MAGIC = 1;
nsresult MakePrefixSet(const uint32_t* aArray, uint32_t aLength);
uint32_t BinSearch(uint32_t start, uint32_t end, uint32_t target);
nsresult LoadFromFd(mozilla::AutoFDClose& fileFd);
nsresult StoreToFd(mozilla::AutoFDClose& fileFd);
// Return the estimated size of the set on disk and in memory, in bytes.
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
// list of fully stored prefixes, that also form the
// start of a run of deltas in mIndexDeltas.
nsTArray<uint32_t> mIndexPrefixes;
// array containing arrays of deltas from indices.
// Index to the place that matches the closest lower
// prefix from mIndexPrefix. Then every "delta" corresponds
// to a prefix in the PrefixSet.
nsTArray<nsTArray<uint16_t> > mIndexDeltas;
// how many prefixes we have.
uint32_t mTotalPrefixes;
// memory report collection might happen while we're updating the prefixset
// on another thread, so pre-compute and remember the size (bug 1050108).
mozilla::Atomic<size_t> mMemoryInUse;
nsCString mMemoryReportPath;
};
#endif
@@ -1,319 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsUrlClassifierProxies.h"
#include "nsUrlClassifierDBService.h"
#include "mozilla/SyncRunnable.h"
using namespace mozilla::safebrowsing;
static nsresult
DispatchToWorkerThread(nsIRunnable* r)
{
nsIThread* t = nsUrlClassifierDBService::BackgroundThread();
if (!t)
return NS_ERROR_FAILURE;
return t->Dispatch(r, NS_DISPATCH_NORMAL);
}
NS_IMPL_ISUPPORTS(UrlClassifierDBServiceWorkerProxy,
nsIUrlClassifierDBServiceWorker)
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::Lookup(nsIPrincipal* aPrincipal,
const nsACString& aTables,
nsIUrlClassifierCallback* aCB)
{
nsCOMPtr<nsIRunnable> r = new LookupRunnable(mTarget, aPrincipal, aTables,
aCB);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::LookupRunnable::Run()
{
(void) mTarget->Lookup(mPrincipal, mLookupTables, mCB);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::GetTables(nsIUrlClassifierCallback* aCB)
{
nsCOMPtr<nsIRunnable> r = new GetTablesRunnable(mTarget, aCB);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::GetTablesRunnable::Run()
{
mTarget->GetTables(mCB);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::SetHashCompleter
(const nsACString&, nsIUrlClassifierHashCompleter*)
{
NS_NOTREACHED("This method should not be called!");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::BeginUpdate
(nsIUrlClassifierUpdateObserver* aUpdater,
const nsACString& aTables)
{
nsCOMPtr<nsIRunnable> r = new BeginUpdateRunnable(mTarget, aUpdater,
aTables);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::BeginUpdateRunnable::Run()
{
mTarget->BeginUpdate(mUpdater, mTables);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::BeginStream(const nsACString& aTable)
{
nsCOMPtr<nsIRunnable> r =
new BeginStreamRunnable(mTarget, aTable);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::BeginStreamRunnable::Run()
{
mTarget->BeginStream(mTable);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::UpdateStream(const nsACString& aUpdateChunk)
{
nsCOMPtr<nsIRunnable> r =
new UpdateStreamRunnable(mTarget, aUpdateChunk);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::UpdateStreamRunnable::Run()
{
mTarget->UpdateStream(mUpdateChunk);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::FinishStream()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(mTarget,
&nsIUrlClassifierDBServiceWorker::FinishStream);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::DoLocalLookupRunnable::Run()
{
mTarget->DoLocalLookup(mSpec, mTables, mResults);
return NS_OK;
}
nsresult
UrlClassifierDBServiceWorkerProxy::DoLocalLookup(const nsACString& spec,
const nsACString& tables,
LookupResultArray* results)
{
// Run synchronously on background thread. NS_DISPATCH_SYNC does *not* do
// what we want -- it continues processing events on the main thread loop
// before the Dispatch returns.
nsCOMPtr<nsIRunnable> r = new DoLocalLookupRunnable(mTarget, spec, tables, results);
nsIThread* t = nsUrlClassifierDBService::BackgroundThread();
if (!t)
return NS_ERROR_FAILURE;
mozilla::SyncRunnable::DispatchToThread(t, r);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::FinishUpdate()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(mTarget,
&nsIUrlClassifierDBServiceWorker::FinishUpdate);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::CancelUpdate()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(mTarget,
&nsIUrlClassifierDBServiceWorker::CancelUpdate);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::ResetDatabase()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(mTarget,
&nsIUrlClassifierDBServiceWorker::ResetDatabase);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::OpenDb()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(mTarget,
&nsIUrlClassifierDBServiceWorker::OpenDb);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::CloseDb()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(mTarget,
&nsIUrlClassifierDBServiceWorker::CloseDb);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::CacheCompletions(CacheResultArray * aEntries)
{
nsCOMPtr<nsIRunnable> r = new CacheCompletionsRunnable(mTarget, aEntries);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::CacheCompletionsRunnable::Run()
{
mTarget->CacheCompletions(mEntries);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::CacheMisses(PrefixArray * aEntries)
{
nsCOMPtr<nsIRunnable> r = new CacheMissesRunnable(mTarget, aEntries);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::CacheMissesRunnable::Run()
{
mTarget->CacheMisses(mEntries);
return NS_OK;
}
NS_IMPL_ISUPPORTS(UrlClassifierLookupCallbackProxy,
nsIUrlClassifierLookupCallback)
NS_IMETHODIMP
UrlClassifierLookupCallbackProxy::LookupComplete
(LookupResultArray * aResults)
{
nsCOMPtr<nsIRunnable> r = new LookupCompleteRunnable(mTarget, aResults);
return NS_DispatchToMainThread(r);
}
NS_IMETHODIMP
UrlClassifierLookupCallbackProxy::LookupCompleteRunnable::Run()
{
mTarget->LookupComplete(mResults);
return NS_OK;
}
NS_IMPL_ISUPPORTS(UrlClassifierCallbackProxy,
nsIUrlClassifierCallback)
NS_IMETHODIMP
UrlClassifierCallbackProxy::HandleEvent(const nsACString& aValue)
{
nsCOMPtr<nsIRunnable> r = new HandleEventRunnable(mTarget, aValue);
return NS_DispatchToMainThread(r);
}
NS_IMETHODIMP
UrlClassifierCallbackProxy::HandleEventRunnable::Run()
{
mTarget->HandleEvent(mValue);
return NS_OK;
}
NS_IMPL_ISUPPORTS(UrlClassifierUpdateObserverProxy,
nsIUrlClassifierUpdateObserver)
NS_IMETHODIMP
UrlClassifierUpdateObserverProxy::UpdateUrlRequested
(const nsACString& aURL,
const nsACString& aTable)
{
nsCOMPtr<nsIRunnable> r =
new UpdateUrlRequestedRunnable(mTarget, aURL, aTable);
return NS_DispatchToMainThread(r);
}
NS_IMETHODIMP
UrlClassifierUpdateObserverProxy::UpdateUrlRequestedRunnable::Run()
{
mTarget->UpdateUrlRequested(mURL, mTable);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierUpdateObserverProxy::StreamFinished(nsresult aStatus,
uint32_t aDelay)
{
nsCOMPtr<nsIRunnable> r =
new StreamFinishedRunnable(mTarget, aStatus, aDelay);
return NS_DispatchToMainThread(r);
}
NS_IMETHODIMP
UrlClassifierUpdateObserverProxy::StreamFinishedRunnable::Run()
{
mTarget->StreamFinished(mStatus, mDelay);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierUpdateObserverProxy::UpdateError(nsresult aError)
{
nsCOMPtr<nsIRunnable> r =
new UpdateErrorRunnable(mTarget, aError);
return NS_DispatchToMainThread(r);
}
NS_IMETHODIMP
UrlClassifierUpdateObserverProxy::UpdateErrorRunnable::Run()
{
mTarget->UpdateError(mError);
return NS_OK;
}
NS_IMETHODIMP
UrlClassifierUpdateObserverProxy::UpdateSuccess(uint32_t aRequestedTimeout)
{
nsCOMPtr<nsIRunnable> r =
new UpdateSuccessRunnable(mTarget, aRequestedTimeout);
return NS_DispatchToMainThread(r);
}
NS_IMETHODIMP
UrlClassifierUpdateObserverProxy::UpdateSuccessRunnable::Run()
{
mTarget->UpdateSuccess(mRequestedTimeout);
return NS_OK;
}
@@ -1,339 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsUrlClassifierProxies_h
#define nsUrlClassifierProxies_h
#include "nsIUrlClassifierDBService.h"
#include "nsUrlClassifierDBService.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
#include "mozilla/Attributes.h"
#include "nsIPrincipal.h"
#include "LookupCache.h"
/**
* Thread proxy from the main thread to the worker thread.
*/
class UrlClassifierDBServiceWorkerProxy final :
public nsIUrlClassifierDBServiceWorker
{
public:
explicit UrlClassifierDBServiceWorkerProxy(nsUrlClassifierDBServiceWorker* aTarget)
: mTarget(aTarget)
{ }
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERDBSERVICE
NS_DECL_NSIURLCLASSIFIERDBSERVICEWORKER
class LookupRunnable : public nsRunnable
{
public:
LookupRunnable(nsUrlClassifierDBServiceWorker* aTarget,
nsIPrincipal* aPrincipal,
const nsACString& aTables,
nsIUrlClassifierCallback* aCB)
: mTarget(aTarget)
, mPrincipal(aPrincipal)
, mLookupTables(aTables)
, mCB(aCB)
{ }
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCString mLookupTables;
nsCOMPtr<nsIUrlClassifierCallback> mCB;
};
class GetTablesRunnable : public nsRunnable
{
public:
GetTablesRunnable(nsUrlClassifierDBServiceWorker* aTarget,
nsIUrlClassifierCallback* aCB)
: mTarget(aTarget)
, mCB(aCB)
{ }
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
nsCOMPtr<nsIUrlClassifierCallback> mCB;
};
class BeginUpdateRunnable : public nsRunnable
{
public:
BeginUpdateRunnable(nsUrlClassifierDBServiceWorker* aTarget,
nsIUrlClassifierUpdateObserver* aUpdater,
const nsACString& aTables)
: mTarget(aTarget)
, mUpdater(aUpdater)
, mTables(aTables)
{ }
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
nsCOMPtr<nsIUrlClassifierUpdateObserver> mUpdater;
nsCString mTables;
};
class BeginStreamRunnable : public nsRunnable
{
public:
BeginStreamRunnable(nsUrlClassifierDBServiceWorker* aTarget,
const nsACString& aTable)
: mTarget(aTarget)
, mTable(aTable)
{ }
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
nsCString mTable;
};
class UpdateStreamRunnable : public nsRunnable
{
public:
UpdateStreamRunnable(nsUrlClassifierDBServiceWorker* aTarget,
const nsACString& aUpdateChunk)
: mTarget(aTarget)
, mUpdateChunk(aUpdateChunk)
{ }
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
nsCString mUpdateChunk;
};
class CacheCompletionsRunnable : public nsRunnable
{
public:
CacheCompletionsRunnable(nsUrlClassifierDBServiceWorker* aTarget,
mozilla::safebrowsing::CacheResultArray *aEntries)
: mTarget(aTarget)
, mEntries(aEntries)
{ }
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
mozilla::safebrowsing::CacheResultArray *mEntries;
};
class CacheMissesRunnable : public nsRunnable
{
public:
CacheMissesRunnable(nsUrlClassifierDBServiceWorker* aTarget,
mozilla::safebrowsing::PrefixArray *aEntries)
: mTarget(aTarget)
, mEntries(aEntries)
{ }
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
mozilla::safebrowsing::PrefixArray *mEntries;
};
class DoLocalLookupRunnable : public nsRunnable
{
public:
DoLocalLookupRunnable(nsUrlClassifierDBServiceWorker* aTarget,
const nsACString& spec,
const nsACString& tables,
mozilla::safebrowsing::LookupResultArray* results)
: mTarget(aTarget)
, mSpec(spec)
, mTables(tables)
, mResults(results)
{ }
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
nsCString mSpec;
nsCString mTables;
mozilla::safebrowsing::LookupResultArray* mResults;
};
public:
nsresult DoLocalLookup(const nsACString& spec,
const nsACString& tables,
mozilla::safebrowsing::LookupResultArray* results);
private:
~UrlClassifierDBServiceWorkerProxy() {}
nsRefPtr<nsUrlClassifierDBServiceWorker> mTarget;
};
// The remaining classes here are all proxies to the main thread
class UrlClassifierLookupCallbackProxy final :
public nsIUrlClassifierLookupCallback
{
public:
explicit UrlClassifierLookupCallbackProxy(nsIUrlClassifierLookupCallback* aTarget)
: mTarget(new nsMainThreadPtrHolder<nsIUrlClassifierLookupCallback>(aTarget))
{ }
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERLOOKUPCALLBACK
class LookupCompleteRunnable : public nsRunnable
{
public:
LookupCompleteRunnable(const nsMainThreadPtrHandle<nsIUrlClassifierLookupCallback>& aTarget,
mozilla::safebrowsing::LookupResultArray *aResults)
: mTarget(aTarget)
, mResults(aResults)
{ }
NS_DECL_NSIRUNNABLE
private:
nsMainThreadPtrHandle<nsIUrlClassifierLookupCallback> mTarget;
mozilla::safebrowsing::LookupResultArray * mResults;
};
private:
~UrlClassifierLookupCallbackProxy() {}
nsMainThreadPtrHandle<nsIUrlClassifierLookupCallback> mTarget;
};
class UrlClassifierCallbackProxy final : public nsIUrlClassifierCallback
{
public:
explicit UrlClassifierCallbackProxy(nsIUrlClassifierCallback* aTarget)
: mTarget(new nsMainThreadPtrHolder<nsIUrlClassifierCallback>(aTarget))
{ }
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERCALLBACK
class HandleEventRunnable : public nsRunnable
{
public:
HandleEventRunnable(const nsMainThreadPtrHandle<nsIUrlClassifierCallback>& aTarget,
const nsACString& aValue)
: mTarget(aTarget)
, mValue(aValue)
{ }
NS_DECL_NSIRUNNABLE
private:
nsMainThreadPtrHandle<nsIUrlClassifierCallback> mTarget;
nsCString mValue;
};
private:
~UrlClassifierCallbackProxy() {}
nsMainThreadPtrHandle<nsIUrlClassifierCallback> mTarget;
};
class UrlClassifierUpdateObserverProxy final :
public nsIUrlClassifierUpdateObserver
{
public:
explicit UrlClassifierUpdateObserverProxy(nsIUrlClassifierUpdateObserver* aTarget)
: mTarget(new nsMainThreadPtrHolder<nsIUrlClassifierUpdateObserver>(aTarget))
{ }
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERUPDATEOBSERVER
class UpdateUrlRequestedRunnable : public nsRunnable
{
public:
UpdateUrlRequestedRunnable(const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver>& aTarget,
const nsACString& aURL,
const nsACString& aTable)
: mTarget(aTarget)
, mURL(aURL)
, mTable(aTable)
{ }
NS_DECL_NSIRUNNABLE
private:
nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget;
nsCString mURL, mTable;
};
class StreamFinishedRunnable : public nsRunnable
{
public:
StreamFinishedRunnable(const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver>& aTarget,
nsresult aStatus, uint32_t aDelay)
: mTarget(aTarget)
, mStatus(aStatus)
, mDelay(aDelay)
{ }
NS_DECL_NSIRUNNABLE
private:
nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget;
nsresult mStatus;
uint32_t mDelay;
};
class UpdateErrorRunnable : public nsRunnable
{
public:
UpdateErrorRunnable(const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver>& aTarget,
nsresult aError)
: mTarget(aTarget)
, mError(aError)
{ }
NS_DECL_NSIRUNNABLE
private:
nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget;
nsresult mError;
};
class UpdateSuccessRunnable : public nsRunnable
{
public:
UpdateSuccessRunnable(const nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver>& aTarget,
uint32_t aRequestedTimeout)
: mTarget(aTarget)
, mRequestedTimeout(aRequestedTimeout)
{ }
NS_DECL_NSIRUNNABLE
private:
nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget;
uint32_t mRequestedTimeout;
};
private:
~UrlClassifierUpdateObserverProxy() {}
nsMainThreadPtrHandle<nsIUrlClassifierUpdateObserver> mTarget;
};
#endif // nsUrlClassifierProxies_h
@@ -1,629 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCRT.h"
#include "nsIHttpChannel.h"
#include "nsIObserverService.h"
#include "nsIStringStream.h"
#include "nsIUploadChannel.h"
#include "nsIURI.h"
#include "nsIUrlClassifierDBService.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "nsToolkitCompsCID.h"
#include "nsUrlClassifierStreamUpdater.h"
#include "prlog.h"
#include "nsIInterfaceRequestor.h"
#include "mozilla/LoadContext.h"
#include "nsContentUtils.h"
static const char* gQuitApplicationMessage = "quit-application";
#undef LOG
// NSPR_LOG_MODULES=UrlClassifierStreamUpdater:5
#if defined(PR_LOGGING)
static const PRLogModuleInfo *gUrlClassifierStreamUpdaterLog = nullptr;
#define LOG(args) PR_LOG(gUrlClassifierStreamUpdaterLog, PR_LOG_DEBUG, args)
#else
#define LOG(args)
#endif
// This class does absolutely nothing, except pass requests onto the DBService.
///////////////////////////////////////////////////////////////////////////////
// nsIUrlClassiferStreamUpdater implementation
// Handles creating/running the stream listener
nsUrlClassifierStreamUpdater::nsUrlClassifierStreamUpdater()
: mIsUpdating(false), mInitialized(false), mDownloadError(false),
mBeganStream(false), mChannel(nullptr)
{
#if defined(PR_LOGGING)
if (!gUrlClassifierStreamUpdaterLog)
gUrlClassifierStreamUpdaterLog = PR_NewLogModule("UrlClassifierStreamUpdater");
#endif
LOG(("nsUrlClassifierStreamUpdater init [this=%p]", this));
}
NS_IMPL_ISUPPORTS(nsUrlClassifierStreamUpdater,
nsIUrlClassifierStreamUpdater,
nsIUrlClassifierUpdateObserver,
nsIRequestObserver,
nsIStreamListener,
nsIObserver,
nsIInterfaceRequestor,
nsITimerCallback)
/**
* Clear out the update.
*/
void
nsUrlClassifierStreamUpdater::DownloadDone()
{
LOG(("nsUrlClassifierStreamUpdater::DownloadDone [this=%p]", this));
mIsUpdating = false;
mPendingUpdates.Clear();
mDownloadError = false;
mSuccessCallback = nullptr;
mUpdateErrorCallback = nullptr;
mDownloadErrorCallback = nullptr;
}
///////////////////////////////////////////////////////////////////////////////
// nsIUrlClassifierStreamUpdater implementation
nsresult
nsUrlClassifierStreamUpdater::FetchUpdate(nsIURI *aUpdateUrl,
const nsACString & aRequestBody,
const nsACString & aStreamTable)
{
#ifdef DEBUG
{
nsCString spec;
aUpdateUrl->GetSpec(spec);
LOG(("Fetching update %s from %s", aRequestBody.Data(), spec.get()));
}
#endif
nsresult rv;
uint32_t loadFlags = nsIChannel::INHIBIT_CACHING |
nsIChannel::LOAD_BYPASS_CACHE;
rv = NS_NewChannel(getter_AddRefs(mChannel),
aUpdateUrl,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_NORMAL,
nsIContentPolicy::TYPE_OTHER,
nullptr, // aLoadGroup
this, // aInterfaceRequestor
loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
mBeganStream = false;
// If aRequestBody is empty, construct it for the test.
if (!aRequestBody.IsEmpty()) {
rv = AddRequestBody(aRequestBody);
NS_ENSURE_SUCCESS(rv, rv);
}
// Set the appropriate content type for file/data URIs, for unit testing
// purposes.
// This is only used for testing and should be deleted.
bool match;
if ((NS_SUCCEEDED(aUpdateUrl->SchemeIs("file", &match)) && match) ||
(NS_SUCCEEDED(aUpdateUrl->SchemeIs("data", &match)) && match)) {
mChannel->SetContentType(NS_LITERAL_CSTRING("application/vnd.google.safebrowsing-update"));
} else {
// We assume everything else is an HTTP request.
// Disable keepalive.
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Connection"), NS_LITERAL_CSTRING("close"), false);
NS_ENSURE_SUCCESS(rv, rv);
}
// Create a custom LoadContext for SafeBrowsing, so we can use callbacks on
// the channel to query the appId which allows separation of safebrowsing
// cookies in a separate jar.
nsCOMPtr<nsIInterfaceRequestor> sbContext =
new mozilla::LoadContext(NECKO_SAFEBROWSING_APP_ID);
rv = mChannel->SetNotificationCallbacks(sbContext);
NS_ENSURE_SUCCESS(rv, rv);
// Make the request.
rv = mChannel->AsyncOpen(this, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
mStreamTable = aStreamTable;
return NS_OK;
}
nsresult
nsUrlClassifierStreamUpdater::FetchUpdate(const nsACString & aUpdateUrl,
const nsACString & aRequestBody,
const nsACString & aStreamTable)
{
LOG(("(pre) Fetching update from %s\n", PromiseFlatCString(aUpdateUrl).get()));
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aUpdateUrl);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString urlSpec;
uri->GetAsciiSpec(urlSpec);
LOG(("(post) Fetching update from %s\n", urlSpec.get()));
return FetchUpdate(uri, aRequestBody, aStreamTable);
}
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::DownloadUpdates(
const nsACString &aRequestTables,
const nsACString &aRequestBody,
const nsACString &aUpdateUrl,
nsIUrlClassifierCallback *aSuccessCallback,
nsIUrlClassifierCallback *aUpdateErrorCallback,
nsIUrlClassifierCallback *aDownloadErrorCallback,
bool *_retval)
{
NS_ENSURE_ARG(aSuccessCallback);
NS_ENSURE_ARG(aUpdateErrorCallback);
NS_ENSURE_ARG(aDownloadErrorCallback);
if (mIsUpdating) {
LOG(("Already updating, queueing update %s from %s", aRequestBody.Data(),
aUpdateUrl.Data()));
*_retval = false;
PendingRequest *request = mPendingRequests.AppendElement();
request->mTables = aRequestTables;
request->mRequest = aRequestBody;
request->mUrl = aUpdateUrl;
request->mSuccessCallback = aSuccessCallback;
request->mUpdateErrorCallback = aUpdateErrorCallback;
request->mDownloadErrorCallback = aDownloadErrorCallback;
return NS_OK;
}
if (aUpdateUrl.IsEmpty()) {
NS_ERROR("updateUrl not set");
return NS_ERROR_NOT_INITIALIZED;
}
nsresult rv;
if (!mInitialized) {
// Add an observer for shutdown so we can cancel any pending list
// downloads. quit-application is the same event that the download
// manager listens for and uses to cancel pending downloads.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (!observerService)
return NS_ERROR_FAILURE;
observerService->AddObserver(this, gQuitApplicationMessage, false);
mDBService = do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
mInitialized = true;
}
rv = mDBService->BeginUpdate(this, aRequestTables);
if (rv == NS_ERROR_NOT_AVAILABLE) {
LOG(("Service busy, already updating, queuing update %s from %s",
aRequestBody.Data(), aUpdateUrl.Data()));
*_retval = false;
PendingRequest *request = mPendingRequests.AppendElement();
request->mTables = aRequestTables;
request->mRequest = aRequestBody;
request->mUrl = aUpdateUrl;
request->mSuccessCallback = aSuccessCallback;
request->mUpdateErrorCallback = aUpdateErrorCallback;
request->mDownloadErrorCallback = aDownloadErrorCallback;
return NS_OK;
}
if (NS_FAILED(rv)) {
return rv;
}
mSuccessCallback = aSuccessCallback;
mUpdateErrorCallback = aUpdateErrorCallback;
mDownloadErrorCallback = aDownloadErrorCallback;
mIsUpdating = true;
*_retval = true;
LOG(("FetchUpdate: %s", aUpdateUrl.Data()));
//LOG(("requestBody: %s", aRequestBody.Data()));
return FetchUpdate(aUpdateUrl, aRequestBody, EmptyCString());
}
///////////////////////////////////////////////////////////////////////////////
// nsIUrlClassifierUpdateObserver implementation
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::UpdateUrlRequested(const nsACString &aUrl,
const nsACString &aTable)
{
LOG(("Queuing requested update from %s\n", PromiseFlatCString(aUrl).get()));
PendingUpdate *update = mPendingUpdates.AppendElement();
if (!update)
return NS_ERROR_OUT_OF_MEMORY;
// Allow data: and file: urls for unit testing purposes, otherwise assume http
if (StringBeginsWith(aUrl, NS_LITERAL_CSTRING("data:")) ||
StringBeginsWith(aUrl, NS_LITERAL_CSTRING("file:"))) {
update->mUrl = aUrl;
} else {
// For unittesting update urls to localhost should use http, not https
// (otherwise the connection will fail silently, since there will be no
// cert available).
if (!StringBeginsWith(aUrl, NS_LITERAL_CSTRING("localhost"))) {
update->mUrl = NS_LITERAL_CSTRING("https://") + aUrl;
} else {
update->mUrl = NS_LITERAL_CSTRING("http://") + aUrl;
}
}
update->mTable = aTable;
return NS_OK;
}
nsresult
nsUrlClassifierStreamUpdater::FetchNext()
{
if (mPendingUpdates.Length() == 0) {
return NS_OK;
}
PendingUpdate &update = mPendingUpdates[0];
LOG(("Fetching update url: %s\n", update.mUrl.get()));
nsresult rv = FetchUpdate(update.mUrl, EmptyCString(),
update.mTable);
if (NS_FAILED(rv)) {
LOG(("Error fetching update url: %s\n", update.mUrl.get()));
// We can commit the urls that we've applied so far. This is
// probably a transient server problem, so trigger backoff.
mDownloadErrorCallback->HandleEvent(EmptyCString());
mDownloadError = true;
mDBService->FinishUpdate();
return rv;
}
mPendingUpdates.RemoveElementAt(0);
return NS_OK;
}
nsresult
nsUrlClassifierStreamUpdater::FetchNextRequest()
{
if (mPendingRequests.Length() == 0) {
LOG(("No more requests, returning"));
return NS_OK;
}
PendingRequest &request = mPendingRequests[0];
LOG(("Stream updater: fetching next request: %s, %s",
request.mTables.get(), request.mUrl.get()));
bool dummy;
DownloadUpdates(
request.mTables,
request.mRequest,
request.mUrl,
request.mSuccessCallback,
request.mUpdateErrorCallback,
request.mDownloadErrorCallback,
&dummy);
request.mSuccessCallback = nullptr;
request.mUpdateErrorCallback = nullptr;
request.mDownloadErrorCallback = nullptr;
mPendingRequests.RemoveElementAt(0);
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::StreamFinished(nsresult status,
uint32_t requestedDelay)
{
// We are a service and may not be reset with Init between calls, so reset
// mBeganStream manually.
mBeganStream = false;
LOG(("nsUrlClassifierStreamUpdater::StreamFinished [%x, %d]", status, requestedDelay));
if (NS_FAILED(status) || mPendingUpdates.Length() == 0) {
// We're done.
LOG(("nsUrlClassifierStreamUpdater::Done [this=%p]", this));
mDBService->FinishUpdate();
return NS_OK;
}
// Wait the requested amount of time before starting a new stream.
// This appears to be a duplicate timer (see bug 1110891)
nsresult rv;
mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
if (NS_SUCCEEDED(rv)) {
rv = mTimer->InitWithCallback(this, requestedDelay,
nsITimer::TYPE_ONE_SHOT);
}
if (NS_FAILED(rv)) {
NS_WARNING("Unable to initialize timer, fetching next safebrowsing item immediately");
return FetchNext();
}
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::UpdateSuccess(uint32_t requestedTimeout)
{
LOG(("nsUrlClassifierStreamUpdater::UpdateSuccess [this=%p]", this));
if (mPendingUpdates.Length() != 0) {
NS_WARNING("Didn't fetch all safebrowsing update redirects");
}
// DownloadDone() clears mSuccessCallback, so we save it off here.
nsCOMPtr<nsIUrlClassifierCallback> successCallback = mDownloadError ? nullptr : mSuccessCallback.get();
DownloadDone();
nsAutoCString strTimeout;
strTimeout.AppendInt(requestedTimeout);
if (successCallback) {
LOG(("nsUrlClassifierStreamUpdater::UpdateSuccess callback [this=%p]",
this));
successCallback->HandleEvent(strTimeout);
} else {
LOG(("nsUrlClassifierStreamUpdater::UpdateSuccess skipping callback [this=%p]",
this));
}
// Now fetch the next request
LOG(("stream updater: calling into fetch next request"));
FetchNextRequest();
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::UpdateError(nsresult result)
{
LOG(("nsUrlClassifierStreamUpdater::UpdateError [this=%p]", this));
// DownloadDone() clears mUpdateErrorCallback, so we save it off here.
nsCOMPtr<nsIUrlClassifierCallback> errorCallback = mDownloadError ? nullptr : mUpdateErrorCallback.get();
DownloadDone();
nsAutoCString strResult;
strResult.AppendInt(static_cast<uint32_t>(result));
if (errorCallback) {
errorCallback->HandleEvent(strResult);
}
return NS_OK;
}
nsresult
nsUrlClassifierStreamUpdater::AddRequestBody(const nsACString &aRequestBody)
{
nsresult rv;
nsCOMPtr<nsIStringInputStream> strStream =
do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = strStream->SetData(aRequestBody.BeginReading(),
aRequestBody.Length());
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(mChannel, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = uploadChannel->SetUploadStream(strStream,
NS_LITERAL_CSTRING("text/plain"),
-1);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
///////////////////////////////////////////////////////////////////////////////
// nsIStreamListenerObserver implementation
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::OnStartRequest(nsIRequest *request,
nsISupports* context)
{
nsresult rv;
bool downloadError = false;
nsAutoCString strStatus;
nsresult status = NS_OK;
// Only update if we got http success header
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
if (httpChannel) {
rv = httpChannel->GetStatus(&status);
LOG(("nsUrlClassifierStreamUpdater::OnStartRequest (status=%x, this=%p)",
status, this));
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(status)) {
// Assume we're overloading the server and trigger backoff.
downloadError = true;
} else {
bool succeeded = false;
rv = httpChannel->GetRequestSucceeded(&succeeded);
NS_ENSURE_SUCCESS(rv, rv);
LOG(("nsUrlClassifierStreamUpdater::OnStartRequest (%s)", succeeded ?
"succeeded" : "failed"));
if (!succeeded) {
// 404 or other error, pass error status back
LOG(("HTTP request returned failure code."));
uint32_t requestStatus;
rv = httpChannel->GetResponseStatus(&requestStatus);
LOG(("HTTP request returned failure code: %d.", requestStatus));
NS_ENSURE_SUCCESS(rv, rv);
strStatus.AppendInt(requestStatus);
downloadError = true;
}
}
}
if (downloadError) {
LOG(("nsUrlClassifierStreamUpdater::Download error [this=%p]", this));
// It's possible for mDownloadErrorCallback to be null on shutdown.
if (mDownloadErrorCallback) {
mDownloadErrorCallback->HandleEvent(strStatus);
}
mDownloadError = true;
status = NS_ERROR_ABORT;
} else if (NS_SUCCEEDED(status)) {
MOZ_ASSERT(mDownloadErrorCallback);
mBeganStream = true;
LOG(("nsUrlClassifierStreamUpdater::Beginning stream [this=%p]", this));
rv = mDBService->BeginStream(mStreamTable);
NS_ENSURE_SUCCESS(rv, rv);
}
mStreamTable.Truncate();
return status;
}
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::OnDataAvailable(nsIRequest *request,
nsISupports* context,
nsIInputStream *aIStream,
uint64_t aSourceOffset,
uint32_t aLength)
{
if (!mDBService)
return NS_ERROR_NOT_INITIALIZED;
LOG(("OnDataAvailable (%d bytes)", aLength));
nsresult rv;
// Copy the data into a nsCString
nsCString chunk;
rv = NS_ConsumeStream(aIStream, aLength, chunk);
NS_ENSURE_SUCCESS(rv, rv);
//LOG(("Chunk (%d): %s\n\n", chunk.Length(), chunk.get()));
rv = mDBService->UpdateStream(chunk);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::OnStopRequest(nsIRequest *request, nsISupports* context,
nsresult aStatus)
{
if (!mDBService)
return NS_ERROR_NOT_INITIALIZED;
LOG(("OnStopRequest (status %x, beganStream %s, this=%p)", aStatus,
mBeganStream ? "true" : "false", this));
nsresult rv;
if (NS_SUCCEEDED(aStatus)) {
// Success, finish this stream and move on to the next.
rv = mDBService->FinishStream();
} else if (mBeganStream) {
LOG(("OnStopRequest::Canceling update [this=%p]", this));
// We began this stream and couldn't finish it. We have to cancel the
// update, it's not in a consistent state.
rv = mDBService->CancelUpdate();
} else {
LOG(("OnStopRequest::Finishing update [this=%p]", this));
// The fetch failed, but we didn't start the stream (probably a
// server or connection error). We can commit what we've applied
// so far, and request again later.
rv = mDBService->FinishUpdate();
}
mChannel = nullptr;
// If the fetch failed, return the network status rather than NS_OK, the
// result of finishing a possibly-empty update
if (NS_SUCCEEDED(aStatus)) {
return rv;
}
return aStatus;
}
///////////////////////////////////////////////////////////////////////////////
// nsIObserver implementation
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::Observe(nsISupports *aSubject, const char *aTopic,
const char16_t *aData)
{
if (nsCRT::strcmp(aTopic, gQuitApplicationMessage) == 0) {
if (mIsUpdating && mChannel) {
LOG(("Cancel download"));
nsresult rv;
rv = mChannel->Cancel(NS_ERROR_ABORT);
NS_ENSURE_SUCCESS(rv, rv);
mIsUpdating = false;
mChannel = nullptr;
}
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
}
return NS_OK;
}
///////////////////////////////////////////////////////////////////////////////
// nsIInterfaceRequestor implementation
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::GetInterface(const nsIID & eventSinkIID, void* *_retval)
{
return QueryInterface(eventSinkIID, _retval);
}
///////////////////////////////////////////////////////////////////////////////
// nsITimerCallback implementation
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::Notify(nsITimer *timer)
{
LOG(("nsUrlClassifierStreamUpdater::Notify [%p]", this));
mTimer = nullptr;
// Start the update process up again.
FetchNext();
return NS_OK;
}
@@ -1,100 +0,0 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsUrlClassifierStreamUpdater_h_
#define nsUrlClassifierStreamUpdater_h_
#include <nsISupportsUtils.h>
#include "nsCOMPtr.h"
#include "nsIObserver.h"
#include "nsIUrlClassifierStreamUpdater.h"
#include "nsIStreamListener.h"
#include "nsNetUtil.h"
#include "nsTArray.h"
#include "nsITimer.h"
#include "mozilla/Attributes.h"
// Forward declare pointers
class nsIURI;
class nsUrlClassifierStreamUpdater final : public nsIUrlClassifierStreamUpdater,
public nsIUrlClassifierUpdateObserver,
public nsIStreamListener,
public nsIObserver,
public nsIInterfaceRequestor,
public nsITimerCallback
{
public:
nsUrlClassifierStreamUpdater();
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERSTREAMUPDATER
NS_DECL_NSIURLCLASSIFIERUPDATEOBSERVER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIOBSERVER
NS_DECL_NSITIMERCALLBACK
private:
// No subclassing
~nsUrlClassifierStreamUpdater() {}
// When the dbservice sends an UpdateComplete or UpdateFailure, we call this
// to reset the stream updater.
void DownloadDone();
// Disallow copy constructor
nsUrlClassifierStreamUpdater(nsUrlClassifierStreamUpdater&);
nsresult AddRequestBody(const nsACString &aRequestBody);
// Fetches an update for a single table.
nsresult FetchUpdate(nsIURI *aURI,
const nsACString &aRequestBody,
const nsACString &aTable);
// Dumb wrapper so we don't have to create URIs.
nsresult FetchUpdate(const nsACString &aURI,
const nsACString &aRequestBody,
const nsACString &aTable);
// Fetches the next table, from mPendingUpdates.
nsresult FetchNext();
// Fetches the next request, from mPendingRequests
nsresult FetchNextRequest();
bool mIsUpdating;
bool mInitialized;
bool mDownloadError;
bool mBeganStream;
nsCString mStreamTable;
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsIUrlClassifierDBService> mDBService;
nsCOMPtr<nsITimer> mTimer;
struct PendingRequest {
nsCString mTables;
nsCString mRequest;
nsCString mUrl;
nsCOMPtr<nsIUrlClassifierCallback> mSuccessCallback;
nsCOMPtr<nsIUrlClassifierCallback> mUpdateErrorCallback;
nsCOMPtr<nsIUrlClassifierCallback> mDownloadErrorCallback;
};
nsTArray<PendingRequest> mPendingRequests;
struct PendingUpdate {
nsCString mUrl;
nsCString mTable;
};
nsTArray<PendingUpdate> mPendingUpdates;
nsCOMPtr<nsIUrlClassifierCallback> mSuccessCallback;
nsCOMPtr<nsIUrlClassifierCallback> mUpdateErrorCallback;
nsCOMPtr<nsIUrlClassifierCallback> mDownloadErrorCallback;
};
#endif // nsUrlClassifierStreamUpdater_h_
@@ -1,364 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsEscape.h"
#include "nsString.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsUrlClassifierUtils.h"
#include "nsTArray.h"
#include "nsReadableUtils.h"
#include "plbase64.h"
#include "prprf.h"
static char int_to_hex_digit(int32_t i)
{
NS_ASSERTION((i >= 0) && (i <= 15), "int too big in int_to_hex_digit");
return static_cast<char>(((i < 10) ? (i + '0') : ((i - 10) + 'A')));
}
static bool
IsDecimal(const nsACString & num)
{
for (uint32_t i = 0; i < num.Length(); i++) {
if (!isdigit(num[i])) {
return false;
}
}
return true;
}
static bool
IsHex(const nsACString & num)
{
if (num.Length() < 3) {
return false;
}
if (num[0] != '0' || !(num[1] == 'x' || num[1] == 'X')) {
return false;
}
for (uint32_t i = 2; i < num.Length(); i++) {
if (!isxdigit(num[i])) {
return false;
}
}
return true;
}
static bool
IsOctal(const nsACString & num)
{
if (num.Length() < 2) {
return false;
}
if (num[0] != '0') {
return false;
}
for (uint32_t i = 1; i < num.Length(); i++) {
if (!isdigit(num[i]) || num[i] == '8' || num[i] == '9') {
return false;
}
}
return true;
}
nsUrlClassifierUtils::nsUrlClassifierUtils() : mEscapeCharmap(nullptr)
{
}
nsresult
nsUrlClassifierUtils::Init()
{
// Everything but alpha numerics, - and .
mEscapeCharmap = new Charmap(0xffffffff, 0xfc009fff, 0xf8000001, 0xf8000001,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff);
if (!mEscapeCharmap)
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
NS_IMPL_ISUPPORTS(nsUrlClassifierUtils, nsIUrlClassifierUtils)
/////////////////////////////////////////////////////////////////////////////
// nsIUrlClassifierUtils
NS_IMETHODIMP
nsUrlClassifierUtils::GetKeyForURI(nsIURI * uri, nsACString & _retval)
{
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
if (!innerURI)
innerURI = uri;
nsAutoCString host;
innerURI->GetAsciiHost(host);
if (host.IsEmpty()) {
return NS_ERROR_MALFORMED_URI;
}
nsresult rv = CanonicalizeHostname(host, _retval);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString path;
rv = innerURI->GetPath(path);
NS_ENSURE_SUCCESS(rv, rv);
// strip out anchors
int32_t ref = path.FindChar('#');
if (ref != kNotFound)
path.SetLength(ref);
nsAutoCString temp;
rv = CanonicalizePath(path, temp);
NS_ENSURE_SUCCESS(rv, rv);
_retval.Append(temp);
return NS_OK;
}
/////////////////////////////////////////////////////////////////////////////
// non-interface methods
nsresult
nsUrlClassifierUtils::CanonicalizeHostname(const nsACString & hostname,
nsACString & _retval)
{
nsAutoCString unescaped;
if (!NS_UnescapeURL(PromiseFlatCString(hostname).get(),
PromiseFlatCString(hostname).Length(),
0, unescaped)) {
unescaped.Assign(hostname);
}
nsAutoCString cleaned;
CleanupHostname(unescaped, cleaned);
nsAutoCString temp;
ParseIPAddress(cleaned, temp);
if (!temp.IsEmpty()) {
cleaned.Assign(temp);
}
ToLowerCase(cleaned);
SpecialEncode(cleaned, false, _retval);
return NS_OK;
}
nsresult
nsUrlClassifierUtils::CanonicalizePath(const nsACString & path,
nsACString & _retval)
{
_retval.Truncate();
nsAutoCString decodedPath(path);
nsAutoCString temp;
while (NS_UnescapeURL(decodedPath.get(), decodedPath.Length(), 0, temp)) {
decodedPath.Assign(temp);
temp.Truncate();
}
SpecialEncode(decodedPath, true, _retval);
// XXX: lowercase the path?
return NS_OK;
}
void
nsUrlClassifierUtils::CleanupHostname(const nsACString & hostname,
nsACString & _retval)
{
_retval.Truncate();
const char* curChar = hostname.BeginReading();
const char* end = hostname.EndReading();
char lastChar = '\0';
while (curChar != end) {
unsigned char c = static_cast<unsigned char>(*curChar);
if (c == '.' && (lastChar == '\0' || lastChar == '.')) {
// skip
} else {
_retval.Append(*curChar);
}
lastChar = c;
++curChar;
}
// cut off trailing dots
while (_retval.Length() > 0 && _retval[_retval.Length() - 1] == '.') {
_retval.SetLength(_retval.Length() - 1);
}
}
void
nsUrlClassifierUtils::ParseIPAddress(const nsACString & host,
nsACString & _retval)
{
_retval.Truncate();
nsACString::const_iterator iter, end;
host.BeginReading(iter);
host.EndReading(end);
if (host.Length() <= 15) {
// The Windows resolver allows a 4-part dotted decimal IP address to
// have a space followed by any old rubbish, so long as the total length
// of the string doesn't get above 15 characters. So, "10.192.95.89 xy"
// is resolved to 10.192.95.89.
// If the string length is greater than 15 characters, e.g.
// "10.192.95.89 xy.wildcard.example.com", it will be resolved through
// DNS.
if (FindCharInReadable(' ', iter, end)) {
end = iter;
}
}
for (host.BeginReading(iter); iter != end; iter++) {
if (!(isxdigit(*iter) || *iter == 'x' || *iter == 'X' || *iter == '.')) {
// not an IP
return;
}
}
host.BeginReading(iter);
nsTArray<nsCString> parts;
ParseString(PromiseFlatCString(Substring(iter, end)), '.', parts);
if (parts.Length() > 4) {
return;
}
// If any potentially-octal numbers (start with 0 but not hex) have
// non-octal digits, no part of the ip can be in octal
// XXX: this came from the old javascript implementation, is it really
// supposed to be like this?
bool allowOctal = true;
uint32_t i;
for (i = 0; i < parts.Length(); i++) {
const nsCString& part = parts[i];
if (part[0] == '0') {
for (uint32_t j = 1; j < part.Length(); j++) {
if (part[j] == 'x') {
break;
}
if (part[j] == '8' || part[j] == '9') {
allowOctal = false;
break;
}
}
}
}
for (i = 0; i < parts.Length(); i++) {
nsAutoCString canonical;
if (i == parts.Length() - 1) {
CanonicalNum(parts[i], 5 - parts.Length(), allowOctal, canonical);
} else {
CanonicalNum(parts[i], 1, allowOctal, canonical);
}
if (canonical.IsEmpty()) {
_retval.Truncate();
return;
}
if (_retval.IsEmpty()) {
_retval.Assign(canonical);
} else {
_retval.Append('.');
_retval.Append(canonical);
}
}
return;
}
void
nsUrlClassifierUtils::CanonicalNum(const nsACString& num,
uint32_t bytes,
bool allowOctal,
nsACString& _retval)
{
_retval.Truncate();
if (num.Length() < 1) {
return;
}
uint32_t val;
if (allowOctal && IsOctal(num)) {
if (PR_sscanf(PromiseFlatCString(num).get(), "%o", &val) != 1) {
return;
}
} else if (IsDecimal(num)) {
if (PR_sscanf(PromiseFlatCString(num).get(), "%u", &val) != 1) {
return;
}
} else if (IsHex(num)) {
if (PR_sscanf(PromiseFlatCString(num).get(), num[1] == 'X' ? "0X%x" : "0x%x",
&val) != 1) {
return;
}
} else {
return;
}
while (bytes--) {
char buf[20];
PR_snprintf(buf, sizeof(buf), "%u", val & 0xff);
if (_retval.IsEmpty()) {
_retval.Assign(buf);
} else {
_retval = nsDependentCString(buf) + NS_LITERAL_CSTRING(".") + _retval;
}
val >>= 8;
}
}
// This function will encode all "special" characters in typical url
// encoding, that is %hh where h is a valid hex digit. It will also fold
// any duplicated slashes.
bool
nsUrlClassifierUtils::SpecialEncode(const nsACString & url,
bool foldSlashes,
nsACString & _retval)
{
bool changed = false;
const char* curChar = url.BeginReading();
const char* end = url.EndReading();
unsigned char lastChar = '\0';
while (curChar != end) {
unsigned char c = static_cast<unsigned char>(*curChar);
if (ShouldURLEscape(c)) {
_retval.Append('%');
_retval.Append(int_to_hex_digit(c / 16));
_retval.Append(int_to_hex_digit(c % 16));
changed = true;
} else if (foldSlashes && (c == '/' && lastChar == '/')) {
// skip
} else {
_retval.Append(*curChar);
}
lastChar = c;
curChar++;
}
return changed;
}
bool
nsUrlClassifierUtils::ShouldURLEscape(const unsigned char c) const
{
return c <= 32 || c == '%' || c >=127;
}
@@ -1,89 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsUrlClassifierUtils_h_
#define nsUrlClassifierUtils_h_
#include "nsAutoPtr.h"
#include "nsIUrlClassifierUtils.h"
#include "nsTArray.h"
#include "nsDataHashtable.h"
#include "mozilla/Attributes.h"
class nsUrlClassifierUtils final : public nsIUrlClassifierUtils
{
private:
/**
* A fast, bit-vector map for ascii characters.
*
* Internally stores 256 bits in an array of 8 ints.
* Does quick bit-flicking to lookup needed characters.
*/
class Charmap
{
public:
Charmap(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3,
uint32_t b4, uint32_t b5, uint32_t b6, uint32_t b7)
{
mMap[0] = b0; mMap[1] = b1; mMap[2] = b2; mMap[3] = b3;
mMap[4] = b4; mMap[5] = b5; mMap[6] = b6; mMap[7] = b7;
}
/**
* Do a quick lookup to see if the letter is in the map.
*/
bool Contains(unsigned char c) const
{
return mMap[c >> 5] & (1 << (c & 31));
}
private:
// Store the 256 bits in an 8 byte array.
uint32_t mMap[8];
};
public:
nsUrlClassifierUtils();
NS_DECL_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERUTILS
nsresult Init();
nsresult CanonicalizeHostname(const nsACString & hostname,
nsACString & _retval);
nsresult CanonicalizePath(const nsACString & url, nsACString & _retval);
// This function will encode all "special" characters in typical url encoding,
// that is %hh where h is a valid hex digit. The characters which are encoded
// by this function are any ascii characters under 32(control characters and
// space), 37(%), and anything 127 or above (special characters). Url is the
// string to encode, ret is the encoded string. Function returns true if
// ret != url.
bool SpecialEncode(const nsACString & url,
bool foldSlashes,
nsACString & _retval);
void ParseIPAddress(const nsACString & host, nsACString & _retval);
void CanonicalNum(const nsACString & num,
uint32_t bytes,
bool allowOctal,
nsACString & _retval);
private:
~nsUrlClassifierUtils() {}
// Disallow copy constructor
nsUrlClassifierUtils(const nsUrlClassifierUtils&);
// Function to tell if we should encode a character.
bool ShouldURLEscape(const unsigned char c) const;
void CleanupHostname(const nsACString & host, nsACString & _retval);
nsAutoPtr<Charmap> mEscapeCharmap;
};
#endif // nsUrlClassifierUtils_h_
@@ -1,326 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdio.h>
#include <ctype.h>
#include "nsEscape.h"
#include "nsString.h"
#include "nsUrlClassifierUtils.h"
#include "nsNetUtil.h"
#include "stdlib.h"
#include "TestHarness.h"
static int gTotalTests = 0;
static int gPassedTests = 0;
static char int_to_hex_digit(int32_t i) {
NS_ASSERTION((i >= 0) && (i <= 15), "int too big in int_to_hex_digit");
return static_cast<char>(((i < 10) ? (i + '0') : ((i - 10) + 'A')));
}
static void CheckEquals(nsCString & expected, nsCString & actual)
{
if (!(expected).Equals((actual))) {
fail("expected |%s| but got |%s|", (expected).get(), (actual).get());
} else {
gPassedTests++;
}
gTotalTests++;
}
void TestUnescapeHelper(const char* in, const char* expected)
{
nsCString out, strIn(in), strExp(expected);
nsUrlClassifierUtils utils;
NS_UnescapeURL(strIn.get(), strIn.Length(), esc_AlwaysCopy, out);
CheckEquals(strExp, out);
}
// Make sure Unescape from nsEncode.h's unescape does what the server does.
void TestUnescape()
{
// test empty string
TestUnescapeHelper("\0", "\0");
// Test docoding of all characters.
nsCString allCharsEncoded, allCharsEncodedLowercase, allCharsAsString;
for (int32_t i = 1; i < 256; ++i) {
allCharsEncoded.Append('%');
allCharsEncoded.Append(int_to_hex_digit(i / 16));
allCharsEncoded.Append((int_to_hex_digit(i % 16)));
allCharsEncodedLowercase.Append('%');
allCharsEncodedLowercase.Append(tolower(int_to_hex_digit(i / 16)));
allCharsEncodedLowercase.Append(tolower(int_to_hex_digit(i % 16)));
allCharsAsString.Append(static_cast<char>(i));
}
nsUrlClassifierUtils utils;
nsCString out;
NS_UnescapeURL(allCharsEncoded.get(), allCharsEncoded.Length(), esc_AlwaysCopy, out);
CheckEquals(allCharsAsString, out);
out.Truncate();
NS_UnescapeURL(allCharsEncodedLowercase.get(), allCharsEncodedLowercase.Length(), esc_AlwaysCopy, out);
CheckEquals(allCharsAsString, out);
// Test %-related edge cases
TestUnescapeHelper("%", "%");
TestUnescapeHelper("%xx", "%xx");
TestUnescapeHelper("%%", "%%");
TestUnescapeHelper("%%%", "%%%");
TestUnescapeHelper("%%%%", "%%%%");
TestUnescapeHelper("%1", "%1");
TestUnescapeHelper("%1z", "%1z");
TestUnescapeHelper("a%1z", "a%1z");
TestUnescapeHelper("abc%d%e%fg%hij%klmno%", "abc%d%e%fg%hij%klmno%");
// A few more tests
TestUnescapeHelper("%25", "%");
TestUnescapeHelper("%25%32%35", "%25");
}
void TestEncodeHelper(const char* in, const char* expected)
{
nsCString out, strIn(in), strExp(expected);
nsUrlClassifierUtils utils;
utils.SpecialEncode(strIn, true, out);
CheckEquals(strExp, out);
}
void TestEnc()
{
// Test empty string
TestEncodeHelper("", "");
// Test that all characters we shouldn't encode ([33-36],[38,126]) are not.
nsCString noenc;
for (int32_t i = 33; i < 127; i++) {
if (i != 37) { // skip %
noenc.Append(static_cast<char>(i));
}
}
nsUrlClassifierUtils utils;
nsCString out;
utils.SpecialEncode(noenc, false, out);
CheckEquals(noenc, out);
// Test that all the chars that we should encode [0,32],37,[127,255] are
nsCString yesAsString, yesExpectedString;
for (int32_t i = 1; i < 256; i++) {
if (i < 33 || i == 37 || i > 126) {
yesAsString.Append(static_cast<char>(i));
yesExpectedString.Append('%');
yesExpectedString.Append(int_to_hex_digit(i / 16));
yesExpectedString.Append(int_to_hex_digit(i % 16));
}
}
out.Truncate();
utils.SpecialEncode(yesAsString, false, out);
CheckEquals(yesExpectedString, out);
TestEncodeHelper("blah//blah", "blah/blah");
}
void TestCanonicalizeHelper(const char* in, const char* expected)
{
nsCString out, strIn(in), strExp(expected);
nsUrlClassifierUtils utils;
utils.CanonicalizePath(strIn, out);
CheckEquals(strExp, out);
}
void TestCanonicalize()
{
// Test repeated %-decoding. Note: %25 --> %, %32 --> 2, %35 --> 5
TestCanonicalizeHelper("%25", "%25");
TestCanonicalizeHelper("%25%32%35", "%25");
TestCanonicalizeHelper("asdf%25%32%35asd", "asdf%25asd");
TestCanonicalizeHelper("%%%25%32%35asd%%", "%25%25%25asd%25%25");
TestCanonicalizeHelper("%25%32%35%25%32%35%25%32%35", "%25%25%25");
TestCanonicalizeHelper("%25", "%25");
TestCanonicalizeHelper("%257Ea%2521b%2540c%2523d%2524e%25f%255E00%252611%252A22%252833%252944_55%252B",
"~a!b@c#d$e%25f^00&11*22(33)44_55+");
TestCanonicalizeHelper("", "");
TestCanonicalizeHelper("%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/%77%77%77%2E%65%62%61%79%2E%63%6F%6D/",
"168.188.99.26/.secure/www.ebay.com/");
TestCanonicalizeHelper("195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/",
"195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/");
// Added in bug 489455. %00 should no longer be changed to %01.
TestCanonicalizeHelper("%00", "%00");
}
void TestParseIPAddressHelper(const char *in, const char *expected)
{
nsCString out, strIn(in), strExp(expected);
nsUrlClassifierUtils utils;
utils.Init();
utils.ParseIPAddress(strIn, out);
CheckEquals(strExp, out);
}
void TestParseIPAddress()
{
TestParseIPAddressHelper("123.123.0.0.1", "");
TestParseIPAddressHelper("255.0.0.1", "255.0.0.1");
TestParseIPAddressHelper("12.0x12.01234", "12.18.2.156");
TestParseIPAddressHelper("276.2.3", "20.2.0.3");
TestParseIPAddressHelper("012.034.01.055", "10.28.1.45");
TestParseIPAddressHelper("0x12.0x43.0x44.0x01", "18.67.68.1");
TestParseIPAddressHelper("167838211", "10.1.2.3");
TestParseIPAddressHelper("3279880203", "195.127.0.11");
TestParseIPAddressHelper("0x12434401", "18.67.68.1");
TestParseIPAddressHelper("413960661", "24.172.137.213");
TestParseIPAddressHelper("03053104725", "24.172.137.213");
TestParseIPAddressHelper("030.0254.0x89d5", "24.172.137.213");
TestParseIPAddressHelper("1.234.4.0377", "1.234.4.255");
TestParseIPAddressHelper("1.2.3.00x0", "");
TestParseIPAddressHelper("10.192.95.89 xy", "10.192.95.89");
TestParseIPAddressHelper("10.192.95.89 xyz", "");
TestParseIPAddressHelper("1.2.3.0x0", "1.2.3.0");
TestParseIPAddressHelper("1.2.3.4", "1.2.3.4");
}
void TestCanonicalNumHelper(const char *in, uint32_t bytes,
bool allowOctal, const char *expected)
{
nsCString out, strIn(in), strExp(expected);
nsUrlClassifierUtils utils;
utils.Init();
utils.CanonicalNum(strIn, bytes, allowOctal, out);
CheckEquals(strExp, out);
}
void TestCanonicalNum()
{
TestCanonicalNumHelper("", 1, true, "");
TestCanonicalNumHelper("10", 0, true, "");
TestCanonicalNumHelper("45", 1, true, "45");
TestCanonicalNumHelper("0x10", 1, true, "16");
TestCanonicalNumHelper("367", 2, true, "1.111");
TestCanonicalNumHelper("012345", 3, true, "0.20.229");
TestCanonicalNumHelper("0173", 1, true, "123");
TestCanonicalNumHelper("09", 1, false, "9");
TestCanonicalNumHelper("0x120x34", 2, true, "");
TestCanonicalNumHelper("0x12fc", 2, true, "18.252");
TestCanonicalNumHelper("3279880203", 4, true, "195.127.0.11");
TestCanonicalNumHelper("0x0000059", 1, true, "89");
TestCanonicalNumHelper("0x00000059", 1, true, "89");
TestCanonicalNumHelper("0x0000067", 1, true, "103");
}
void TestHostnameHelper(const char *in, const char *expected)
{
nsCString out, strIn(in), strExp(expected);
nsUrlClassifierUtils utils;
utils.Init();
utils.CanonicalizeHostname(strIn, out);
CheckEquals(strExp, out);
}
void TestHostname()
{
TestHostnameHelper("abcd123;[]", "abcd123;[]");
TestHostnameHelper("abc.123", "abc.123");
TestHostnameHelper("abc..123", "abc.123");
TestHostnameHelper("trailing.", "trailing");
TestHostnameHelper("i love trailing dots....", "i%20love%20trailing%20dots");
TestHostnameHelper(".leading", "leading");
TestHostnameHelper("..leading", "leading");
TestHostnameHelper(".dots.", "dots");
TestHostnameHelper(".both.", "both");
TestHostnameHelper(".both..", "both");
TestHostnameHelper("..both.", "both");
TestHostnameHelper("..both..", "both");
TestHostnameHelper("..a.b.c.d..", "a.b.c.d");
TestHostnameHelper("..127.0.0.1..", "127.0.0.1");
TestHostnameHelper("asdf!@#$a", "asdf!@#$a");
TestHostnameHelper("AB CD 12354", "ab%20cd%2012354");
TestHostnameHelper("\1\2\3\4\112\177", "%01%02%03%04j%7F");
TestHostnameHelper("<>.AS/-+", "<>.as/-+");
// Added in bug 489455. %00 should no longer be changed to %01.
TestHostnameHelper("%00", "%00");
}
void TestLongHostname()
{
static const int kTestSize = 1024 * 150;
char *str = static_cast<char*>(malloc(kTestSize + 1));
memset(str, 'x', kTestSize);
str[kTestSize] = '\0';
nsUrlClassifierUtils utils;
utils.Init();
nsAutoCString out;
nsDependentCString in(str);
PRIntervalTime clockStart = PR_IntervalNow();
utils.CanonicalizeHostname(in, out);
PRIntervalTime clockEnd = PR_IntervalNow();
CheckEquals(in, out);
printf("CanonicalizeHostname on long string (%dms)\n",
PR_IntervalToMilliseconds(clockEnd - clockStart));
}
void TestFragmentSet()
{
nsUrlClassifierFragmentSet set;
set.Init(3);
set.Put(NS_LITERAL_CSTRING("a"));
set.Put(NS_LITERAL_CSTRING("b"));
set.Put(NS_LITERAL_CSTRING("c"));
// At this point, adding a fourth element would push "a" off.
// Make sure that set.Has("a") moves it to the front of the list
set.Has(NS_LITERAL_CSTRING("a"));
// Now add a new item. This should now push "b" off the list,
// but leave "a"
set.Put(NS_LITERAL_CSTRING("d"));
gTotalTests++;
if (set.Has(NS_LITERAL_CSTRING("a")))
gPassedTests++;
else
fail("set.Has(\"a\") failed.");
gTotalTests++;
if (!set.Has(NS_LITERAL_CSTRING("b")))
gPassedTests++;
else
fail("!set.Has(\"b\") failed.");
}
int main(int argc, char **argv)
{
ScopedXPCOM xpcom("URLClassiferUtils");
TestUnescape();
TestEnc();
TestCanonicalize();
TestCanonicalNum();
TestParseIPAddress();
TestHostname();
TestLongHostname();
TestFragmentSet();
if (gPassedTests == gTotalTests)
passed(__FILE__);
printf("%d of %d tests passed\n", gPassedTests, gTotalTests);
// Non-zero return status signals test failure to build system.
return (gPassedTests != gTotalTests);
}
@@ -1,2 +0,0 @@
toolkit.jar:
+ content/global/url-classifier/unittests.xul (unittests.xul)
@@ -1,72 +0,0 @@
<html>
<head>
<title></title>
<script type="text/javascript">
// Modified by evil.js
var scriptItem;
var scriptItem1 = "untouched";
var imageItem1 = "untouched";
var frameItem1 = "untouched";
var scriptItem2 = "untouched";
var imageItem2 = "untouched";
var frameItem2 = "untouched";
function checkLoads() {
window.parent.is(scriptItem1, "spoiled", "Should not block tracking js 1");
window.parent.is(scriptItem2, "spoiled", "Should not block tracking js 2");
window.parent.is(imageItem1, "spoiled", "Should not block tracking img 1");
window.parent.is(imageItem2, "spoiled", "Should not block tracking img 2");
window.parent.is(frameItem1, "spoiled", "Should not block tracking iframe 1");
window.parent.is(frameItem2, "spoiled", "Should not block tracking iframe 2");
window.parent.is(window.document.blockedTrackingNodeCount, 0,
"No elements should be blocked");
// End (parent) test.
window.parent.clearPermissions();
window.parent.SimpleTest.finish();
}
</script>
</head>
<body onload="checkLoads()">
<!-- Try loading from a tracking script URI (1) -->
<script id="badscript1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="scriptItem1 = 'spoiled';"></script>
<!-- Try loading from a tracking image URI (1) -->
<img id="badimage1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg" onload="imageItem1 = 'spoiled';"/>
<!-- Try loading from a tracking frame URI (1) -->
<iframe id="badframe1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/track.html" onload="frameItem1 = 'spoiled';"></iframe>
<script>
// Try loading from a tracking script URI (2) - The loader may follow a
// different path depending on whether the resource is loaded from JS or HTML.
var newScript = document.createElement("script");
newScript.id = "badscript2";
newScript.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js";
newScript.addEventListener("load", function onload() {scriptItem2 = 'spoiled';});
document.body.appendChild(newScript);
/// Try loading from a tracking image URI (2)
var newImage = document.createElement("img");
newImage.id = "badimage2";
newImage.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg";
newImage.addEventListener("load", function onload() {imageItem2 = 'spoiled'});
document.body.appendChild(newImage);
// Try loading from a tracking iframe URI (2)
var newFrame = document.createElement("iframe");
newFrame.id = "badframe2";
newFrame.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/track.html"
newFrame.addEventListener("load", function onload() {frameItem2 = 'spoiled'});
document.body.appendChild(newFrame);
</script>
</body>
</html>
@@ -1,8 +0,0 @@
[DEFAULT]
support-files =
allowlistAnnotatedFrame.html
classifiedAnnotatedFrame.html
[test_lookup_system_principal.html]
[test_classified_annotations.html]
[test_allowlisted_annotations.html]
@@ -1,135 +0,0 @@
<html>
<head>
<title></title>
<script type="text/javascript">
"use strict";
var scriptItem = "untouched";
var scriptItem1 = "untouched";
var scriptItem2 = "untouched";
var imageItem1 = "untouched";
var imageItem2 = "untouched";
var frameItem1 = "untouched";
var frameItem2 = "untouched";
var badids = [
"badscript1",
"badscript2",
"badimage1",
"badimage2",
"badframe1",
"badframe2",
"badcss"
];
function checkLoads() {
window.parent.is(
scriptItem1, "untouched", "Should not load tracking javascript");
window.parent.is(
scriptItem2, "untouched", "Should not load tracking javascript (2)");
window.parent.is(
imageItem1, "untouched", "Should not load tracking images");
window.parent.is(
imageItem2, "untouched", "Should not load tracking images (2)");
window.parent.is(
frameItem1, "untouched", "Should not load tracking iframes");
window.parent.is(
frameItem2, "untouched", "Should not load tracking iframes (2)");
var elt = document.getElementById("styleCheck");
var style = document.defaultView.getComputedStyle(elt, "");
window.parent.isnot(
style.visibility, "hidden", "Should not load tracking css");
window.parent.is(window.document.blockedTrackingNodeCount, badids.length,
"Should identify all tracking elements");
var blockedTrackingNodes = window.document.blockedTrackingNodes;
// Make sure that every node in blockedTrackingNodes exists in the tree
// (that may not always be the case but do not expect any nodes to disappear
// from the tree here)
var allNodeMatch = true;
for (var i = 0; i < blockedTrackingNodes.length; i++) {
var nodeMatch = false;
for (var j = 0; j < badids.length && !nodeMatch; j++) {
nodeMatch |=
(blockedTrackingNodes[i] == document.getElementById(badids[j]));
}
allNodeMatch &= nodeMatch;
}
window.parent.is(allNodeMatch, true,
"All annotated nodes are expected in the tree");
// Make sure that every node with a badid (see badids) is found in the
// blockedTrackingNodes. This tells us if we are neglecting to annotate
// some nodes
allNodeMatch = true;
for (var j = 0; j < badids.length; j++) {
var nodeMatch = false;
for (var i = 0; i < blockedTrackingNodes.length && !nodeMatch; i++) {
nodeMatch |=
(blockedTrackingNodes[i] == document.getElementById(badids[j]));
}
allNodeMatch &= nodeMatch;
}
window.parent.is(allNodeMatch, true,
"All tracking nodes are expected to be annotated as such");
// Unset prefs, etc.
window.parent.cleanup();
// End (parent) test.
window.parent.SimpleTest.finish();
}
</script>
<!-- Try loading from a tracking CSS URI -->
<link id="badcss" rel="stylesheet" type="text/css" href="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link>
</head>
<body onload="checkLoads()">
<!-- Try loading from a tracking script URI (1): evil.js onload will have updated the scriptItem variable -->
<script id="badscript1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="scriptItem1 = scriptItem;"></script>
<!-- Try loading from a tracking image URI (1) -->
<img id="badimage1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?reload=true" onload="imageItem1 = 'spoiled';"/>
<!-- Try loading from a tracking frame URI (1) -->
<iframe id="badframe1" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/track.html" onload="frameItem1 = 'spoiled';"></iframe>
<script>
// Try loading from a tracking script URI (2) - The loader may follow a different path depending on whether the resource is loaded from JS or HTML.
var newScript = document.createElement("script");
newScript.id = "badscript2";
newScript.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js";
newScript.addEventListener("load", function() {scriptItem2 = scriptItem;});
document.body.appendChild(newScript);
/// Try loading from a tracking image URI (2)
var newImage = document.createElement("img");
newImage.id = "badimage2";
newImage.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?reload=true";
newImage.addEventListener("load", function() {imageItem2 = 'spoiled'});
document.body.appendChild(newImage);
// Try loading from a tracking iframe URI (2)
var newFrame = document.createElement("iframe");
newFrame.id = "badframe2";
newFrame.src = "http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/track.html"
newFrame.addEventListener("load", function() {frameItem2 = 'spoiled'});
document.body.appendChild(newFrame);
</script>
The following should not be hidden:
<div id="styleCheck">STYLE TEST</div>
</body>
</html>
@@ -1,48 +0,0 @@
<html>
<head>
<title></title>
<script type="text/javascript">
var scriptItem = "untouched";
function checkLoads() {
// Make sure the javascript did not load.
window.parent.is(scriptItem, "untouched", "Should not load bad javascript");
// Make sure the css did not load.
var elt = document.getElementById("styleCheck");
var style = document.defaultView.getComputedStyle(elt, "");
window.parent.isnot(style.visibility, "hidden", "Should not load bad css");
// Call parent.loadTestFrame again to test classification metadata in HTTP
// cache entries.
if (window.parent.firstLoad) {
window.parent.info("Reloading from cache...");
window.parent.firstLoad = false;
window.parent.loadTestFrame();
return;
}
// End (parent) test.
window.parent.SimpleTest.finish();
}
</script>
<!-- Try loading from a malware javascript URI -->
<script type="text/javascript" src="http://malware.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"></script>
<!-- Try loading from a malware css URI -->
<link rel="stylesheet" type="text/css" href="http://malware.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link>
<!-- XXX How is this part of the test supposed to work (= be checked)? -->
<!-- Try loading a marked-as-malware css through an @import from a clean URI -->
<link rel="stylesheet" type="text/css" href="import.css"></link>
</head>
<body onload="checkLoads()">
The following should not be hidden:
<div id="styleCheck">STYLE TEST</div>
</body>
</html>
@@ -1,10 +0,0 @@
onmessage = function() {
try {
importScripts("evilWorker.js");
} catch(ex) {
postMessage("success");
return;
}
postMessage("failure");
};
@@ -1 +0,0 @@
#styleCheck { visibility: hidden; }
@@ -1 +0,0 @@
scriptItem = "loaded malware javascript!";
@@ -1,3 +0,0 @@
onmessage = function() {
postMessage("loaded bad file");
}
@@ -1,3 +0,0 @@
/* malware.example.com is in the malware database.
classifierBad.css does not actually exist. */
@import url("http://malware.example.com/tests/docshell/test/classifierBad.css");
@@ -1,15 +0,0 @@
[DEFAULT]
skip-if = buildapp == 'b2g' || e10s
support-files =
classifierFrame.html
cleanWorker.js
evil.css
evil.js
evilWorker.js
import.css
raptor.jpg
track.html
workerFrame.html
[test_classifier.html]
[test_classifier_worker.html]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

@@ -1,86 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test the URI Classifier</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
var Cc = SpecialPowers.Cc;
var Ci = SpecialPowers.Ci;
// Add https://allowlisted.example.com to the permissions manager
SpecialPowers.addPermission("trackingprotection",
Ci.nsIPermissionManager.ALLOW_ACTION,
{ url: "https://allowlisted.example.com" });
// Add some URLs to the tracking database
var testData = "tracking.example.com/";
var testUpdate =
"n:1000\ni:test-track-simple\nad:1\n" +
"a:524:32:" + testData.length + "\n" +
testData;
var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
.getService(Ci.nsIUrlClassifierDBService);
function clearPermissions() {
SpecialPowers.removePermission("trackingprotection",
{ url: "https://allowlisted.example.com" });
ok(!SpecialPowers.testPermission("trackingprotection",
Ci.nsIPermissionManager.ALLOW_ACTION,
{ url: "https://allowlisted.example.com" }));
}
function doUpdate(update) {
var listener = {
QueryInterface: function(iid)
{
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
updateUrlRequested: function(url) { },
streamFinished: function(status) { },
updateError: function(errorCode) {
ok(false, "Couldn't update classifier.");
// Abort test.
SimpleTest.finish();
},
updateSuccess: function(requestedTimeout) {
document.getElementById("testFrame").src = "allowlistAnnotatedFrame.html";
}
};
dbService.beginUpdate(listener, "test-track-simple", "");
dbService.beginStream("", "");
dbService.updateStream(update);
dbService.finishStream();
dbService.finishUpdate();
}
SpecialPowers.pushPrefEnv(
{"set" : [["urlclassifier.trackingTable", "test-track-simple"],
["privacy.trackingprotection.enabled", true],
["channelclassifier.allowlist_example", true]]},
function() { doUpdate(testUpdate); });
// Expected finish() call is in "allowlistedAnnotatedFrame.html".
SimpleTest.waitForExplicitFinish();
</script>
</pre>
<iframe id="testFrame" width="100%" height="100%" onload=""></iframe>
</body>
</html>
@@ -1,81 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test the URI Classifier</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
var Cc = SpecialPowers.Cc;
var Ci = SpecialPowers.Ci;
// Add some URLs to the tracking database
var testData = "tracking.example.com/";
var testUpdate =
"n:1000\ni:test-track-simple\nad:1\n" +
"a:524:32:" + testData.length + "\n" +
testData;
function cleanup() {
SpecialPowers.clearUserPref("privacy.trackingprotection.enabled");
SpecialPowers.clearUserPref("channelclassifier.allowlist_example");
}
var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
.getService(Ci.nsIUrlClassifierDBService);
function doUpdate(update) {
var listener = {
QueryInterface: function(iid)
{
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
updateUrlRequested: function(url) { },
streamFinished: function(status) { },
updateError: function(errorCode) {
ok(false, "Couldn't update classifier.");
// Abort test.
SimpleTest.finish();
},
updateSuccess: function(requestedTimeout) {
SpecialPowers.setBoolPref("privacy.trackingprotection.enabled", true);
// Make sure chrome:// URIs are processed. This does not white-list
// any URIs unless 'https://allowlisted.example.com' is added in the
// permission manager (see test_allowlisted_annotations.html)
SpecialPowers.setBoolPref("channelclassifier.allowlist_example", true);
document.getElementById("testFrame").src = "classifiedAnnotatedFrame.html";
}
};
dbService.beginUpdate(listener, "test-track-simple", "");
dbService.beginStream("", "");
dbService.updateStream(update);
dbService.finishStream();
dbService.finishUpdate();
}
SpecialPowers.pushPrefEnv(
{"set" : [["urlclassifier.trackingTable", "test-track-simple"]]},
function() { doUpdate(testUpdate); });
// Expected finish() call is in "classifiedAnnotatedFrame.html".
SimpleTest.waitForExplicitFinish();
</script>
</pre>
<iframe id="testFrame" width="100%" height="100%" onload=""></iframe>
</body>
</html>
@@ -1,78 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test the URI Classifier</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
var Cc = SpecialPowers.Cc;
var Ci = SpecialPowers.Ci;
var firstLoad = true;
// Add some URLs to the malware database.
var testData = "malware.example.com/";
var testUpdate =
"n:1000\ni:test-malware-simple\nad:1\n" +
"a:524:32:" + testData.length + "\n" +
testData;
var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
.getService(Ci.nsIUrlClassifierDBService);
function loadTestFrame() {
document.getElementById("testFrame").src = "classifierFrame.html";
}
function doUpdate(update) {
var listener = {
QueryInterface: SpecialPowers.wrapCallback(function(iid)
{
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}),
updateUrlRequested: function(url) { },
streamFinished: function(status) { },
updateError: function(errorCode) {
ok(false, "Couldn't update classifier.");
// Abort test.
SimpleTest.finish();
},
updateSuccess: function(requestedTimeout) {
SpecialPowers.pushPrefEnv(
{"set" : [["browser.safebrowsing.malware.enabled", true]]},
loadTestFrame);
}
};
dbService.beginUpdate(listener, "test-malware-simple", "");
dbService.beginStream("", "");
dbService.updateStream(update);
dbService.finishStream();
dbService.finishUpdate();
}
SpecialPowers.pushPrefEnv(
{"set" : [["urlclassifier.malwareTable", "test-malware-simple"],
["urlclassifier.phishTable", "test-phish-simple"]]},
function() { doUpdate(testUpdate); });
// Expected finish() call is in "classifierFrame.html".
SimpleTest.waitForExplicitFinish();
</script>
</pre>
<iframe id="testFrame" onload=""></iframe>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More