Bug 1384753 - Move Application Reputation files into a new component. r?francois draft
authorDimiL <dlee@mozilla.com>
Mon, 23 Oct 2017 16:18:52 +0800
changeset 688376 51748b6abb90361f98a33cf93ac088359cd08993
parent 687186 aa958b29c149a67fce772f8473e9586e71fbdb46
child 688377 7a3b1495b5cc1505096e75a9e4d1214ca01e7c98
push id86730
push userbmo:dlee@mozilla.com
push dateMon, 30 Oct 2017 01:24:04 +0000
reviewersfrancois
bugs1384753
milestone58.0a1
Bug 1384753 - Move Application Reputation files into a new component. r?francois
browser/installer/package-manifest.in
mobile/android/installer/package-manifest.in
toolkit/components/build/moz.build
toolkit/components/build/nsToolkitCompsCID.h
toolkit/components/downloads/ApplicationReputation.cpp
toolkit/components/downloads/ApplicationReputation.h
toolkit/components/downloads/chromium/LICENSE
toolkit/components/downloads/chromium/chrome/common/safe_browsing/csd.pb.cc
toolkit/components/downloads/chromium/chrome/common/safe_browsing/csd.pb.h
toolkit/components/downloads/chromium/chrome/common/safe_browsing/csd.proto
toolkit/components/downloads/generate_csd.sh
toolkit/components/downloads/moz.build
toolkit/components/downloads/nsDownloadManager.cpp
toolkit/components/downloads/nsIApplicationReputation.idl
toolkit/components/downloads/test/unit/.eslintrc.js
toolkit/components/downloads/test/unit/data/block_digest.chunk
toolkit/components/downloads/test/unit/data/digest.chunk
toolkit/components/downloads/test/unit/data/signed_win.exe
toolkit/components/downloads/test/unit/head_download_manager.js
toolkit/components/downloads/test/unit/test_app_rep.js
toolkit/components/downloads/test/unit/test_app_rep_maclinux.js
toolkit/components/downloads/test/unit/test_app_rep_windows.js
toolkit/components/downloads/test/unit/xpcshell.ini
toolkit/components/jsdownloads/src/DownloadIntegration.jsm
toolkit/components/moz.build
toolkit/components/reputationservice/ApplicationReputation.cpp
toolkit/components/reputationservice/ApplicationReputation.h
toolkit/components/reputationservice/chromium/LICENSE
toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.pb.cc
toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.pb.h
toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.proto
toolkit/components/reputationservice/generate_csd.sh
toolkit/components/reputationservice/moz.build
toolkit/components/reputationservice/nsIApplicationReputation.idl
toolkit/components/reputationservice/test/unit/.eslintrc.js
toolkit/components/reputationservice/test/unit/data/block_digest.chunk
toolkit/components/reputationservice/test/unit/data/digest.chunk
toolkit/components/reputationservice/test/unit/data/signed_win.exe
toolkit/components/reputationservice/test/unit/head_download_manager.js
toolkit/components/reputationservice/test/unit/test_app_rep.js
toolkit/components/reputationservice/test/unit/test_app_rep_maclinux.js
toolkit/components/reputationservice/test/unit/test_app_rep_windows.js
toolkit/components/reputationservice/test/unit/xpcshell.ini
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -289,16 +289,17 @@
 @RESPATH@/components/places.xpt
 @RESPATH@/components/plugin.xpt
 @RESPATH@/components/pref.xpt
 @RESPATH@/components/prefetch.xpt
 #ifdef MOZ_GECKO_PROFILER
 @RESPATH@/components/profiler.xpt
 #endif
 @RESPATH@/components/rdf.xpt
+@RESPATH@/components/reputationservice.xpt
 @RESPATH@/components/satchel.xpt
 @RESPATH@/components/saxparser.xpt
 @RESPATH@/browser/components/sessionstore.xpt
 @RESPATH@/components/services-crypto-component.xpt
 @RESPATH@/components/captivedetect.xpt
 @RESPATH@/browser/components/shellservice.xpt
 @RESPATH@/components/shistory.xpt
 @RESPATH@/components/spellchecker.xpt
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -203,16 +203,17 @@
 #endif
 @BINPATH@/components/plugin.xpt
 @BINPATH@/components/pref.xpt
 @BINPATH@/components/prefetch.xpt
 #ifdef MOZ_GECKO_PROFILER
 @BINPATH@/components/profiler.xpt
 #endif
 @BINPATH@/components/rdf.xpt
+@BINPATH@/components/reputationservice.xpt
 @BINPATH@/components/satchel.xpt
 @BINPATH@/components/saxparser.xpt
 @BINPATH@/components/services-crypto-component.xpt
 @BINPATH@/components/captivedetect.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
--- a/toolkit/components/build/moz.build
+++ b/toolkit/components/build/moz.build
@@ -21,16 +21,17 @@ LOCAL_INCLUDES += [
     '../../xre',
     '../alerts',
     '../downloads',
     '../feeds',
     '../find',
     '../jsdownloads/src',
     '../perfmonitoring',
     '../protobuf',
+    '../reputationservice',
     '../startup',
     '../statusfilter',
     '../typeaheadfind',
     '../url-classifier',
 ]
 
 if not CONFIG['MOZ_DISABLE_PARENTAL_CONTROLS']:
     LOCAL_INCLUDES += [
--- a/toolkit/components/build/nsToolkitCompsCID.h
+++ b/toolkit/components/build/nsToolkitCompsCID.h
@@ -171,20 +171,20 @@
 { 0x984e3259, 0x9266, 0x49cf, { 0xb6, 0x05, 0x60, 0xb0, 0x22, 0xa0, 0x07, 0x56 } }
 
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
 #define NS_UPDATEPROCESSOR_CID \
 { 0xf3dcf644, 0x79e8, 0x4f59, { 0xa1, 0xbb, 0x87, 0x84, 0x54, 0x48, 0x8e, 0xf9 } }
 #endif
 
 #define NS_APPLICATION_REPUTATION_SERVICE_CONTRACTID \
-  "@mozilla.org/downloads/application-reputation-service;1"
+  "@mozilla.org/reputationservice/application-reputation-service;1"
 
 #define NS_APPLICATION_REPUTATION_SERVICE_CID \
-{ 0x8576c950, 0xf4a2, 0x11e2, { 0xb7, 0x78, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
+{ 0xd21b4c33, 0x716f, 0x4117, { 0x80, 0x41, 0x27, 0x70, 0xb5, 0x9f, 0xf8, 0xa6 } }
 
 #define NS_ADDONCONTENTPOLICY_CID \
 { 0xc26a8241, 0xecf4, 0x4aed, { 0x9f, 0x3c, 0xf1, 0xf5, 0xc7, 0x13, 0xb9, 0xa5 } }
 
 #define NS_ADDON_PATH_SERVICE_CID \
 { 0xa39f39d0, 0xdfb6, 0x11e3, { 0x8b, 0x68, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
 
 #define NS_ADDON_POLICY_SERVICE_CID \
deleted file mode 100755
--- a/toolkit/components/downloads/generate_csd.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env bash
-
-# A script to generate
-# chromium/chrome/common/safe_browsing/csd.pb.{cc,h} for use in
-# nsIApplicationReputationQuery. This script assumes you have
-# downloaded and installed the protocol buffer compiler.
-
-set -e
-
-if [ "${PROTOC_PATH:+set}" != "set" ]; 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://github.com/google/protobuf/releases"
-  exit 1
-fi
-
-if [ ! -f nsIApplicationReputation.idl ]; then
-    echo "You must run this script in the toolkit/components/downloads" >&2
-    echo "directory of the source tree." >&2
-    exit 1
-fi
-
-# Get the protocol buffer and compile it
-CSD_PROTO_URL="https://chromium.googlesource.com/chromium/src/+/master/chrome/common/safe_browsing/csd.proto?format=TEXT"
-CSD_PATH="chromium/chrome/common/safe_browsing"
-
-curl "$CSD_PROTO_URL" | base64 --decode > "$CSD_PATH"/csd.proto
-"$PROTOC_PATH" "$CSD_PATH"/csd.proto --cpp_out=.
--- a/toolkit/components/downloads/moz.build
+++ b/toolkit/components/downloads/moz.build
@@ -2,53 +2,24 @@
 # 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/.
 
 with Files('*'):
     BUG_COMPONENT = ('Toolkit', 'Download Manager')
 
-with Files('ApplicationReputation.*'):
-    BUG_COMPONENT = ('Toolkit', 'Safe Browsing')
-
-with Files('chromium/*'):
-    BUG_COMPONENT = ('Toolkit', 'Safe Browsing')
-
-with Files('generate_csd.sh'):
-    BUG_COMPONENT = ('Toolkit', 'Safe Browsing')
-
-with Files('nsIApplicationReputation.idl'):
-    BUG_COMPONENT = ('Toolkit', 'Safe Browsing')
-
-XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
-
 XPIDL_SOURCES += [
-    'nsIApplicationReputation.idl',
     'nsIDownload.idl',
     'nsIDownloadManager.idl',
     'nsIDownloadManagerUI.idl',
     'nsIDownloadProgressListener.idl',
 ]
 
 XPIDL_MODULE = 'downloads'
 
 UNIFIED_SOURCES += [
-    'ApplicationReputation.cpp',
-    'chromium/chrome/common/safe_browsing/csd.pb.cc',
     'nsDownloadManager.cpp'
 ]
 
 FINAL_LIBRARY = 'xul'
 
-LOCAL_INCLUDES += [
-    '../protobuf',
-    '/ipc/chromium/src',
-    'chromium'
-]
-
-DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
-DEFINES['GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER'] = True
-
 CXXFLAGS += CONFIG['TK_CFLAGS']
-
-if CONFIG['GNU_CXX']:
-    CXXFLAGS += ['-Wno-shadow']
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -4,16 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIPrefService.h"
 #include "nsIPropertyBag2.h"
 #include "nsCExternalHandlerService.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDownloadManager.h"
 
+#include "mozilla/Services.h"
+
 using namespace mozilla;
 
 #define DOWNLOAD_MANAGER_BUNDLE "chrome://mozapps/locale/downloads/downloads.properties"
 
 #define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsDownloadManager
deleted file mode 100644
--- a/toolkit/components/downloads/test/unit/test_app_rep.js
+++ /dev/null
@@ -1,346 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
-
-Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-const gAppRep = Cc["@mozilla.org/downloads/application-reputation-service;1"].
-                  getService(Ci.nsIApplicationReputationService);
-var gHttpServ = null;
-var gTables = {};
-
-var ALLOW_LIST = 0;
-var BLOCK_LIST = 1;
-var NO_LIST = 2;
-
-var whitelistedURI = createURI("http://foo:bar@whitelisted.com/index.htm#junk");
-var exampleURI = createURI("http://user:password@example.com/i.html?foo=bar");
-var blocklistedURI = createURI("http://baz:qux@blocklisted.com?xyzzy");
-
-const appRepURLPref = "browser.safebrowsing.downloads.remote.url";
-
-function readFileToString(aFilename) {
-  let f = do_get_file(aFilename);
-  let stream = Cc["@mozilla.org/network/file-input-stream;1"]
-                 .createInstance(Ci.nsIFileInputStream);
-  stream.init(f, -1, 0, 0);
-  let buf = NetUtil.readInputStreamToString(stream, stream.available());
-  return buf;
-}
-
-// Registers a table for which to serve update chunks. Returns a promise that
-// resolves when that chunk has been downloaded.
-function registerTableUpdate(aTable, aFilename) {
-  // If we haven't been given an update for this table yet, add it to the map
-  if (!(aTable in gTables)) {
-    gTables[aTable] = [];
-  }
-
-  // The number of chunks associated with this table.
-  let numChunks = gTables[aTable].length + 1;
-  let redirectPath = "/" + aTable + "-" + numChunks;
-  let redirectUrl = "localhost:4444" + redirectPath;
-
-  // Store redirect url for that table so we can return it later when we
-  // process an update request.
-  gTables[aTable].push(redirectUrl);
-
-  gHttpServ.registerPathHandler(redirectPath, function(request, response) {
-    do_print("Mock safebrowsing server handling request for " + redirectPath);
-    let contents = readFileToString(aFilename);
-    do_print("Length of " + aFilename + ": " + contents.length);
-    response.setHeader("Content-Type",
-                       "application/vnd.google.safebrowsing-update", false);
-    response.setStatusLine(request.httpVersion, 200, "OK");
-    response.bodyOutputStream.write(contents, contents.length);
-  });
-}
-
-add_task(async function test_setup() {
-  // Set up a local HTTP server to return bad verdicts.
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  // Ensure safebrowsing is enabled for this test, even if the app
-  // doesn't have it enabled.
-  Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true);
-  Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", true);
-  do_register_cleanup(function() {
-    Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled");
-    Services.prefs.clearUserPref("browser.safebrowsing.downloads.enabled");
-  });
-
-  // Set block and allow tables explicitly, since the allowlist is normally
-  // disabled on non-Windows platforms.
-  Services.prefs.setCharPref("urlclassifier.downloadBlockTable",
-                             "goog-badbinurl-shavar");
-  Services.prefs.setCharPref("urlclassifier.downloadAllowTable",
-                             "goog-downloadwhite-digest256");
-  do_register_cleanup(function() {
-    Services.prefs.clearUserPref("urlclassifier.downloadBlockTable");
-    Services.prefs.clearUserPref("urlclassifier.downloadAllowTable");
-  });
-
-  gHttpServ = new HttpServer();
-  gHttpServ.registerDirectory("/", do_get_cwd());
-  gHttpServ.registerPathHandler("/download", function(request, response) {
-    do_throw("This test should never make a remote lookup");
-  });
-  gHttpServ.start(4444);
-
-  do_register_cleanup(function() {
-    return (async function() {
-      await new Promise(resolve => {
-        gHttpServ.stop(resolve);
-      });
-    })();
-  });
-});
-
-function check_telemetry(aShouldBlockCount,
-                         aListCounts) {
-  let local = Cc["@mozilla.org/base/telemetry;1"]
-                .getService(Ci.nsITelemetry)
-                .getHistogramById("APPLICATION_REPUTATION_LOCAL")
-                .snapshot();
-  do_check_eq(local.counts[ALLOW_LIST], aListCounts[ALLOW_LIST],
-              "Allow list counts don't match");
-  do_check_eq(local.counts[BLOCK_LIST], aListCounts[BLOCK_LIST],
-              "Block list counts don't match");
-  do_check_eq(local.counts[NO_LIST], aListCounts[NO_LIST],
-              "No list counts don't match");
-
-  let shouldBlock = Cc["@mozilla.org/base/telemetry;1"]
-                .getService(Ci.nsITelemetry)
-                .getHistogramById("APPLICATION_REPUTATION_SHOULD_BLOCK")
-                .snapshot();
-  // SHOULD_BLOCK = true
-  do_check_eq(shouldBlock.counts[1], aShouldBlockCount);
-}
-
-function get_telemetry_counts() {
-  let local = Cc["@mozilla.org/base/telemetry;1"]
-                .getService(Ci.nsITelemetry)
-                .getHistogramById("APPLICATION_REPUTATION_LOCAL")
-                .snapshot();
-  let shouldBlock = Cc["@mozilla.org/base/telemetry;1"]
-                .getService(Ci.nsITelemetry)
-                .getHistogramById("APPLICATION_REPUTATION_SHOULD_BLOCK")
-                .snapshot();
-  return { shouldBlock: shouldBlock.counts[1],
-           listCounts: local.counts };
-}
-
-add_test(function test_nullSourceURI() {
-  let counts = get_telemetry_counts();
-  gAppRep.queryReputation({
-    // No source URI
-    fileSize: 12,
-  }, function onComplete(aShouldBlock, aStatus) {
-    do_check_eq(Cr.NS_ERROR_UNEXPECTED, aStatus);
-    do_check_false(aShouldBlock);
-    check_telemetry(counts.shouldBlock, counts.listCounts);
-    run_next_test();
-  });
-});
-
-add_test(function test_nullCallback() {
-  let counts = get_telemetry_counts();
-  try {
-    gAppRep.queryReputation({
-      sourceURI: createURI("http://example.com"),
-      fileSize: 12,
-    }, null);
-    do_throw("Callback cannot be null");
-  } catch (ex) {
-    if (ex.result != Cr.NS_ERROR_INVALID_POINTER)
-      throw ex;
-    // We don't even increment the count here, because there's no callback.
-    check_telemetry(counts.shouldBlock, counts.listCounts);
-    run_next_test();
-  }
-});
-
-// Set up the local whitelist.
-add_test(function test_local_list() {
-  // Construct a response with redirect urls.
-  function processUpdateRequest() {
-    let response = "n:1000\n";
-    for (let table in gTables) {
-      response += "i:" + table + "\n";
-      for (let i = 0; i < gTables[table].length; ++i) {
-        response += "u:" + gTables[table][i] + "\n";
-      }
-    }
-    do_print("Returning update response: " + response);
-    return response;
-  }
-  gHttpServ.registerPathHandler("/downloads", function(request, response) {
-    let blob = processUpdateRequest();
-    response.setHeader("Content-Type",
-                       "application/vnd.google.safebrowsing-update", false);
-    response.setStatusLine(request.httpVersion, 200, "OK");
-    response.bodyOutputStream.write(blob, blob.length);
-  });
-
-  let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
-    .getService(Ci.nsIUrlClassifierStreamUpdater);
-
-  // Load up some update chunks for the safebrowsing server to serve.
-  // This chunk contains the hash of blocklisted.com/.
-  registerTableUpdate("goog-badbinurl-shavar", "data/block_digest.chunk");
-  // This chunk contains the hash of whitelisted.com/.
-  registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk");
-
-  // Download some updates, and don't continue until the downloads are done.
-  function updateSuccess(aEvent) {
-    // Timeout of n:1000 is constructed in processUpdateRequest above and
-    // passed back in the callback in nsIUrlClassifierStreamUpdater on success.
-    do_check_eq("1000", aEvent);
-    do_print("All data processed");
-    run_next_test();
-  }
-  // Just throw if we ever get an update or download error.
-  function handleError(aEvent) {
-    do_throw("We didn't download or update correctly: " + aEvent);
-  }
-  streamUpdater.downloadUpdates(
-    "goog-downloadwhite-digest256,goog-badbinurl-shavar",
-    "goog-downloadwhite-digest256,goog-badbinurl-shavar;\n",
-    true, // isPostRequest.
-    "http://localhost:4444/downloads",
-    updateSuccess, handleError, handleError);
-});
-
-add_test(function test_unlisted() {
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  let counts = get_telemetry_counts();
-  let listCounts = counts.listCounts;
-  listCounts[NO_LIST]++;
-  gAppRep.queryReputation({
-    sourceURI: exampleURI,
-    fileSize: 12,
-  }, function onComplete(aShouldBlock, aStatus) {
-    do_check_eq(Cr.NS_OK, aStatus);
-    do_check_false(aShouldBlock);
-    check_telemetry(counts.shouldBlock, listCounts);
-    run_next_test();
-  });
-});
-
-add_test(function test_non_uri() {
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  let counts = get_telemetry_counts();
-  let listCounts = counts.listCounts;
-  // No listcount is incremented, since the sourceURI is not an nsIURL
-  let source = NetUtil.newURI("data:application/octet-stream,ABC");
-  do_check_false(source instanceof Ci.nsIURL);
-  gAppRep.queryReputation({
-    sourceURI: source,
-    fileSize: 12,
-  }, function onComplete(aShouldBlock, aStatus) {
-    do_check_eq(Cr.NS_OK, aStatus);
-    do_check_false(aShouldBlock);
-    check_telemetry(counts.shouldBlock, listCounts);
-    run_next_test();
-  });
-});
-
-add_test(function test_local_blacklist() {
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  let counts = get_telemetry_counts();
-  let listCounts = counts.listCounts;
-  listCounts[BLOCK_LIST]++;
-  gAppRep.queryReputation({
-    sourceURI: blocklistedURI,
-    fileSize: 12,
-  }, function onComplete(aShouldBlock, aStatus) {
-    do_check_eq(Cr.NS_OK, aStatus);
-    do_check_true(aShouldBlock);
-    check_telemetry(counts.shouldBlock + 1, listCounts);
-    run_next_test();
-  });
-});
-
-add_test(function test_referer_blacklist() {
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  let counts = get_telemetry_counts();
-  let listCounts = counts.listCounts;
-  listCounts[BLOCK_LIST]++;
-  gAppRep.queryReputation({
-    sourceURI: exampleURI,
-    referrerURI: blocklistedURI,
-    fileSize: 12,
-  }, function onComplete(aShouldBlock, aStatus) {
-    do_check_eq(Cr.NS_OK, aStatus);
-    do_check_true(aShouldBlock);
-    check_telemetry(counts.shouldBlock + 1, listCounts);
-    run_next_test();
-  });
-});
-
-add_test(function test_blocklist_trumps_allowlist() {
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  let counts = get_telemetry_counts();
-  let listCounts = counts.listCounts;
-  listCounts[BLOCK_LIST]++;
-  gAppRep.queryReputation({
-    sourceURI: whitelistedURI,
-    referrerURI: blocklistedURI,
-    fileSize: 12,
-  }, function onComplete(aShouldBlock, aStatus) {
-    do_check_eq(Cr.NS_OK, aStatus);
-    do_check_true(aShouldBlock);
-    check_telemetry(counts.shouldBlock + 1, listCounts);
-    run_next_test();
-  });
-});
-
-add_test(function test_redirect_on_blocklist() {
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  let counts = get_telemetry_counts();
-  let listCounts = counts.listCounts;
-  listCounts[BLOCK_LIST]++;
-  listCounts[ALLOW_LIST]++;
-  let secman = Services.scriptSecurityManager;
-  let badRedirects = Cc["@mozilla.org/array;1"]
-                       .createInstance(Ci.nsIMutableArray);
-
-  let redirect1 = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIRedirectHistoryEntry]),
-    principal: secman.createCodebasePrincipal(exampleURI, {}),
-  };
-  badRedirects.appendElement(redirect1);
-
-  let redirect2 = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIRedirectHistoryEntry]),
-    principal: secman.createCodebasePrincipal(blocklistedURI, {}),
-  };
-  badRedirects.appendElement(redirect2);
-
-  let redirect3 = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIRedirectHistoryEntry]),
-    principal: secman.createCodebasePrincipal(whitelistedURI, {}),
-  };
-  badRedirects.appendElement(redirect3);
-
-  gAppRep.queryReputation({
-    sourceURI: whitelistedURI,
-    referrerURI: exampleURI,
-    redirects: badRedirects,
-    fileSize: 12,
-  }, function onComplete(aShouldBlock, aStatus) {
-    do_check_eq(Cr.NS_OK, aStatus);
-    do_check_true(aShouldBlock);
-    check_telemetry(counts.shouldBlock + 1, listCounts);
-    run_next_test();
-  });
-});
deleted file mode 100644
--- a/toolkit/components/downloads/test/unit/test_app_rep_maclinux.js
+++ /dev/null
@@ -1,294 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests signature extraction using Windows Authenticode APIs of
- * downloaded files.
- */
-
-// Globals
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-
-const gAppRep = Cc["@mozilla.org/downloads/application-reputation-service;1"].
-                  getService(Ci.nsIApplicationReputationService);
-var gStillRunning = true;
-var gTables = {};
-var gHttpServer = null;
-
-const appRepURLPref = "browser.safebrowsing.downloads.remote.url";
-const remoteEnabledPref = "browser.safebrowsing.downloads.remote.enabled";
-
-function readFileToString(aFilename) {
-  let f = do_get_file(aFilename);
-  let stream = Cc["@mozilla.org/network/file-input-stream;1"]
-                 .createInstance(Ci.nsIFileInputStream);
-  stream.init(f, -1, 0, 0);
-  let buf = NetUtil.readInputStreamToString(stream, stream.available());
-  return buf;
-}
-
-function registerTableUpdate(aTable, aFilename) {
-  // If we haven't been given an update for this table yet, add it to the map
-  if (!(aTable in gTables)) {
-    gTables[aTable] = [];
-  }
-
-  // The number of chunks associated with this table.
-  let numChunks = gTables[aTable].length + 1;
-  let redirectPath = "/" + aTable + "-" + numChunks;
-  let redirectUrl = "localhost:4444" + redirectPath;
-
-  // Store redirect url for that table so we can return it later when we
-  // process an update request.
-  gTables[aTable].push(redirectUrl);
-
-  gHttpServer.registerPathHandler(redirectPath, function(request, response) {
-    do_print("Mock safebrowsing server handling request for " + redirectPath);
-    let contents = readFileToString(aFilename);
-    do_print("Length of " + aFilename + ": " + contents.length);
-    response.setHeader("Content-Type",
-                       "application/vnd.google.safebrowsing-update", false);
-    response.setStatusLine(request.httpVersion, 200, "OK");
-    response.bodyOutputStream.write(contents, contents.length);
-  });
-}
-
-// Tests
-
-add_task(function test_setup() {
-  // Wait 10 minutes, that is half of the external xpcshell timeout.
-  do_timeout(10 * 60 * 1000, function() {
-    if (gStillRunning) {
-      do_throw("Test timed out.");
-    }
-  });
-  // Set up a local HTTP server to return bad verdicts.
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  // Ensure safebrowsing is enabled for this test, even if the app
-  // doesn't have it enabled.
-  Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true);
-  Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", true);
-  // Set block table explicitly, no need for the allow table though
-  Services.prefs.setCharPref("urlclassifier.downloadBlockTable",
-                             "goog-badbinurl-shavar");
-  // SendRemoteQueryInternal needs locale preference.
-  let locale = Services.prefs.getCharPref("general.useragent.locale");
-  Services.prefs.setCharPref("general.useragent.locale", "en-US");
-
-  do_register_cleanup(function() {
-    Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled");
-    Services.prefs.clearUserPref("browser.safebrowsing.downloads.enabled");
-    Services.prefs.clearUserPref("urlclassifier.downloadBlockTable");
-    Services.prefs.setCharPref("general.useragent.locale", locale);
-  });
-
-  gHttpServer = new HttpServer();
-  gHttpServer.registerDirectory("/", do_get_cwd());
-
-  function createVerdict(aShouldBlock) {
-    // We can't programmatically create a protocol buffer here, so just
-    // hardcode some already serialized ones.
-    let blob = String.fromCharCode(parseInt(0x08, 16));
-    if (aShouldBlock) {
-      // A safe_browsing::ClientDownloadRequest with a DANGEROUS verdict
-      blob += String.fromCharCode(parseInt(0x01, 16));
-    } else {
-      // A safe_browsing::ClientDownloadRequest with a SAFE verdict
-      blob += String.fromCharCode(parseInt(0x00, 16));
-    }
-    return blob;
-  }
-
-  gHttpServer.registerPathHandler("/throw", function(request, response) {
-    do_throw("We shouldn't be getting here");
-  });
-
-  gHttpServer.registerPathHandler("/download", function(request, response) {
-    do_print("Querying remote server for verdict");
-    response.setHeader("Content-Type", "application/octet-stream", false);
-    let buf = NetUtil.readInputStreamToString(
-      request.bodyInputStream,
-      request.bodyInputStream.available());
-    do_print("Request length: " + buf.length);
-    // A garbage response. By default this produces NS_CANNOT_CONVERT_DATA as
-    // the callback status.
-    let blob = "this is not a serialized protocol buffer (the length doesn't match our hard-coded values)";
-    // We can't actually parse the protocol buffer here, so just switch on the
-    // length instead of inspecting the contents.
-    if (buf.length == 67) {
-      // evil.com
-      blob = createVerdict(true);
-    } else if (buf.length == 73) {
-      // mozilla.com
-      blob = createVerdict(false);
-    }
-    response.bodyOutputStream.write(blob, blob.length);
-  });
-
-  gHttpServer.start(4444);
-
-  do_register_cleanup(function() {
-    return (async function() {
-      await new Promise(resolve => {
-        gHttpServer.stop(resolve);
-      });
-    })();
-  });
-});
-
-// Construct a response with redirect urls.
-function processUpdateRequest() {
-  let response = "n:1000\n";
-  for (let table in gTables) {
-    response += "i:" + table + "\n";
-    for (let i = 0; i < gTables[table].length; ++i) {
-      response += "u:" + gTables[table][i] + "\n";
-    }
-  }
-  do_print("Returning update response: " + response);
-  return response;
-}
-
-// Set up the local whitelist.
-function waitForUpdates() {
-  return new Promise((resolve, reject) => {
-    gHttpServer.registerPathHandler("/downloads", function(request, response) {
-      let blob = processUpdateRequest();
-      response.setHeader("Content-Type",
-                         "application/vnd.google.safebrowsing-update", false);
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.bodyOutputStream.write(blob, blob.length);
-    });
-
-    let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
-      .getService(Ci.nsIUrlClassifierStreamUpdater);
-
-    // Load up some update chunks for the safebrowsing server to serve. This
-    // particular chunk contains the hash of whitelisted.com/ and
-    // sb-ssl.google.com/safebrowsing/csd/certificate/.
-    registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk");
-
-    // Resolve the promise once processing the updates is complete.
-    function updateSuccess(aEvent) {
-      // Timeout of n:1000 is constructed in processUpdateRequest above and
-      // passed back in the callback in nsIUrlClassifierStreamUpdater on success.
-      do_check_eq("1000", aEvent);
-      do_print("All data processed");
-      resolve(true);
-    }
-    // Just throw if we ever get an update or download error.
-    function handleError(aEvent) {
-      do_throw("We didn't download or update correctly: " + aEvent);
-      reject();
-    }
-    streamUpdater.downloadUpdates(
-      "goog-downloadwhite-digest256",
-      "goog-downloadwhite-digest256;\n",
-      true,
-      "http://localhost:4444/downloads",
-      updateSuccess, handleError, handleError);
-  });
-}
-
-function promiseQueryReputation(query, expectedShouldBlock) {
-  return new Promise(resolve => {
-    function onComplete(aShouldBlock, aStatus) {
-      do_check_eq(Cr.NS_OK, aStatus);
-      do_check_eq(aShouldBlock, expectedShouldBlock);
-      resolve(true);
-    }
-    gAppRep.queryReputation(query, onComplete);
-  });
-}
-
-add_task(async function() {
-  // Wait for Safebrowsing local list updates to complete.
-  await waitForUpdates();
-});
-
-add_task(async function test_blocked_binary() {
-  // We should reach the remote server for a verdict.
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             true);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  // evil.com should return a malware verdict from the remote server.
-  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
-                                suggestedFileName: "noop.bat",
-                                fileSize: 12}, true);
-});
-
-add_task(async function test_non_binary() {
-  // We should not reach the remote server for a verdict for non-binary files.
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             true);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/throw");
-  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
-                                suggestedFileName: "noop.txt",
-                                fileSize: 12}, false);
-});
-
-add_task(async function test_good_binary() {
-  // We should reach the remote server for a verdict.
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             true);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  // mozilla.com should return a not-guilty verdict from the remote server.
-  await promiseQueryReputation({sourceURI: createURI("http://mozilla.com"),
-                                suggestedFileName: "noop.bat",
-                                fileSize: 12}, false);
-});
-
-add_task(async function test_disabled() {
-  // Explicitly disable remote checks
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             false);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/throw");
-  let query = {sourceURI: createURI("http://example.com"),
-               suggestedFileName: "noop.bat",
-               fileSize: 12};
-  await new Promise(resolve => {
-    gAppRep.queryReputation(query,
-      function onComplete(aShouldBlock, aStatus) {
-        // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
-        do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
-        do_check_false(aShouldBlock);
-        resolve(true);
-      }
-    );
-  });
-});
-
-add_task(async function test_disabled_through_lists() {
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             false);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  Services.prefs.setCharPref("urlclassifier.downloadBlockTable", "");
-  let query = {sourceURI: createURI("http://example.com"),
-               suggestedFileName: "noop.bat",
-               fileSize: 12};
-  await new Promise(resolve => {
-    gAppRep.queryReputation(query,
-      function onComplete(aShouldBlock, aStatus) {
-        // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
-        do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
-        do_check_false(aShouldBlock);
-        resolve(true);
-      }
-    );
-  });
-});
-add_task(async function test_teardown() {
-  gStillRunning = false;
-});
deleted file mode 100644
--- a/toolkit/components/downloads/test/unit/test_app_rep_windows.js
+++ /dev/null
@@ -1,421 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests signature extraction using Windows Authenticode APIs of
- * downloaded files.
- */
-
-// Globals
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
-                                  "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-
-const BackgroundFileSaverOutputStream = Components.Constructor(
-      "@mozilla.org/network/background-file-saver;1?mode=outputstream",
-      "nsIBackgroundFileSaver");
-
-const StringInputStream = Components.Constructor(
-      "@mozilla.org/io/string-input-stream;1",
-      "nsIStringInputStream",
-      "setData");
-
-const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt";
-
-const gAppRep = Cc["@mozilla.org/downloads/application-reputation-service;1"].
-                  getService(Ci.nsIApplicationReputationService);
-var gStillRunning = true;
-var gTables = {};
-var gHttpServer = null;
-
-const appRepURLPref = "browser.safebrowsing.downloads.remote.url";
-const remoteEnabledPref = "browser.safebrowsing.downloads.remote.enabled";
-
-/**
- * Returns a reference to a temporary file.  If the file is then created, it
- * will be removed when tests in this file finish.
- */
-function getTempFile(aLeafName) {
-  let file = FileUtils.getFile("TmpD", [aLeafName]);
-  do_register_cleanup(function GTF_cleanup() {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-  return file;
-}
-
-function readFileToString(aFilename) {
-  let f = do_get_file(aFilename);
-  let stream = Cc["@mozilla.org/network/file-input-stream;1"]
-                 .createInstance(Ci.nsIFileInputStream);
-  stream.init(f, -1, 0, 0);
-  let buf = NetUtil.readInputStreamToString(stream, stream.available());
-  return buf;
-}
-
-/**
- * Waits for the given saver object to complete.
- *
- * @param aSaver
- *        The saver, with the output stream or a stream listener implementation.
- * @param aOnTargetChangeFn
- *        Optional callback invoked with the target file name when it changes.
- *
- * @return {Promise}
- * @resolves When onSaveComplete is called with a success code.
- * @rejects With an exception, if onSaveComplete is called with a failure code.
- */
-function promiseSaverComplete(aSaver, aOnTargetChangeFn) {
-  return new Promise((resolve, reject) => {
-    aSaver.observer = {
-      onTargetChange: function BFSO_onSaveComplete(unused, aTarget) {
-        if (aOnTargetChangeFn) {
-          aOnTargetChangeFn(aTarget);
-        }
-      },
-      onSaveComplete: function BFSO_onSaveComplete(unused, aStatus) {
-        if (Components.isSuccessCode(aStatus)) {
-          resolve();
-        } else {
-          reject(new Components.Exception("Saver failed.", aStatus));
-        }
-      },
-    };
-  });
-}
-
-/**
- * Feeds a string to a BackgroundFileSaverOutputStream.
- *
- * @param aSourceString
- *        The source data to copy.
- * @param aSaverOutputStream
- *        The BackgroundFileSaverOutputStream to feed.
- * @param aCloseWhenDone
- *        If true, the output stream will be closed when the copy finishes.
- *
- * @return {Promise}
- * @resolves When the copy completes with a success code.
- * @rejects With an exception, if the copy fails.
- */
-function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) {
-  return new Promise((resolve, reject) => {
-    let inputStream = new StringInputStream(aSourceString, aSourceString.length);
-    let copier = Cc["@mozilla.org/network/async-stream-copier;1"]
-                 .createInstance(Ci.nsIAsyncStreamCopier);
-    copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true,
-                aCloseWhenDone);
-    copier.asyncCopy({
-      onStartRequest() { },
-      onStopRequest(aRequest, aContext, aStatusCode) {
-        if (Components.isSuccessCode(aStatusCode)) {
-          resolve();
-        } else {
-          reject(new Components.Exception(aStatusCode));
-        }
-      },
-    }, null);
-  });
-}
-
-// Registers a table for which to serve update chunks.
-function registerTableUpdate(aTable, aFilename) {
-  // If we haven't been given an update for this table yet, add it to the map
-  if (!(aTable in gTables)) {
-    gTables[aTable] = [];
-  }
-
-  // The number of chunks associated with this table.
-  let numChunks = gTables[aTable].length + 1;
-  let redirectPath = "/" + aTable + "-" + numChunks;
-  let redirectUrl = "localhost:4444" + redirectPath;
-
-  // Store redirect url for that table so we can return it later when we
-  // process an update request.
-  gTables[aTable].push(redirectUrl);
-
-  gHttpServer.registerPathHandler(redirectPath, function(request, response) {
-    do_print("Mock safebrowsing server handling request for " + redirectPath);
-    let contents = readFileToString(aFilename);
-    do_print("Length of " + aFilename + ": " + contents.length);
-    response.setHeader("Content-Type",
-                       "application/vnd.google.safebrowsing-update", false);
-    response.setStatusLine(request.httpVersion, 200, "OK");
-    response.bodyOutputStream.write(contents, contents.length);
-  });
-}
-
-// Tests
-
-add_task(async function test_setup() {
-  // Wait 10 minutes, that is half of the external xpcshell timeout.
-  do_timeout(10 * 60 * 1000, function() {
-    if (gStillRunning) {
-      do_throw("Test timed out.");
-    }
-  });
-  // Set up a local HTTP server to return bad verdicts.
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  // Ensure safebrowsing is enabled for this test, even if the app
-  // doesn't have it enabled.
-  Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true);
-  Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", true);
-  // Set block and allow tables explicitly, since the allowlist is normally
-  // disabled on comm-central.
-  Services.prefs.setCharPref("urlclassifier.downloadBlockTable",
-                             "goog-badbinurl-shavar");
-  Services.prefs.setCharPref("urlclassifier.downloadAllowTable",
-                             "goog-downloadwhite-digest256");
-  // SendRemoteQueryInternal needs locale preference.
-  let locale = Services.prefs.getCharPref("general.useragent.locale");
-  Services.prefs.setCharPref("general.useragent.locale", "en-US");
-
-  do_register_cleanup(function() {
-    Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled");
-    Services.prefs.clearUserPref("browser.safebrowsing.downloads.enabled");
-    Services.prefs.clearUserPref("urlclassifier.downloadBlockTable");
-    Services.prefs.clearUserPref("urlclassifier.downloadAllowTable");
-    Services.prefs.setCharPref("general.useragent.locale", locale);
-  });
-
-  gHttpServer = new HttpServer();
-  gHttpServer.registerDirectory("/", do_get_cwd());
-
-  function createVerdict(aShouldBlock) {
-    // We can't programmatically create a protocol buffer here, so just
-    // hardcode some already serialized ones.
-    let blob = String.fromCharCode(parseInt(0x08, 16));
-    if (aShouldBlock) {
-      // A safe_browsing::ClientDownloadRequest with a DANGEROUS verdict
-      blob += String.fromCharCode(parseInt(0x01, 16));
-    } else {
-      // A safe_browsing::ClientDownloadRequest with a SAFE verdict
-      blob += String.fromCharCode(parseInt(0x00, 16));
-    }
-    return blob;
-  }
-
-  gHttpServer.registerPathHandler("/throw", function(request, response) {
-    do_throw("We shouldn't be getting here");
-  });
-
-  gHttpServer.registerPathHandler("/download", function(request, response) {
-    do_print("Querying remote server for verdict");
-    response.setHeader("Content-Type", "application/octet-stream", false);
-    let buf = NetUtil.readInputStreamToString(
-      request.bodyInputStream,
-      request.bodyInputStream.available());
-    do_print("Request length: " + buf.length);
-    // A garbage response. By default this produces NS_CANNOT_CONVERT_DATA as
-    // the callback status.
-    let blob = "this is not a serialized protocol buffer (the length doesn't match our hard-coded values)";
-    // We can't actually parse the protocol buffer here, so just switch on the
-    // length instead of inspecting the contents.
-    if (buf.length == 67) {
-      // evil.com
-      blob = createVerdict(true);
-    } else if (buf.length == 73) {
-      // mozilla.com
-      blob = createVerdict(false);
-    }
-    response.bodyOutputStream.write(blob, blob.length);
-  });
-
-  gHttpServer.start(4444);
-
-  do_register_cleanup(function() {
-    return (async function() {
-      await new Promise(resolve => {
-        gHttpServer.stop(resolve);
-      });
-    })();
-  });
-});
-
-// Construct a response with redirect urls.
-function processUpdateRequest() {
-  let response = "n:1000\n";
-  for (let table in gTables) {
-    response += "i:" + table + "\n";
-    for (let i = 0; i < gTables[table].length; ++i) {
-      response += "u:" + gTables[table][i] + "\n";
-    }
-  }
-  do_print("Returning update response: " + response);
-  return response;
-}
-
-// Set up the local whitelist.
-function waitForUpdates() {
-  return new Promise((resolve, reject) => {
-    gHttpServer.registerPathHandler("/downloads", function(request, response) {
-      let blob = processUpdateRequest();
-      response.setHeader("Content-Type",
-                         "application/vnd.google.safebrowsing-update", false);
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.bodyOutputStream.write(blob, blob.length);
-    });
-
-    let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
-      .getService(Ci.nsIUrlClassifierStreamUpdater);
-
-    // Load up some update chunks for the safebrowsing server to serve. This
-    // particular chunk contains the hash of whitelisted.com/ and
-    // sb-ssl.google.com/safebrowsing/csd/certificate/.
-    registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk");
-
-    // Resolve the promise once processing the updates is complete.
-    function updateSuccess(aEvent) {
-      // Timeout of n:1000 is constructed in processUpdateRequest above and
-      // passed back in the callback in nsIUrlClassifierStreamUpdater on success.
-      do_check_eq("1000", aEvent);
-      do_print("All data processed");
-      resolve(true);
-    }
-    // Just throw if we ever get an update or download error.
-    function handleError(aEvent) {
-      do_throw("We didn't download or update correctly: " + aEvent);
-      reject();
-    }
-    streamUpdater.downloadUpdates(
-      "goog-downloadwhite-digest256",
-      "goog-downloadwhite-digest256;\n",
-      true,
-      "http://localhost:4444/downloads",
-      updateSuccess, handleError, handleError);
-  });
-}
-
-function promiseQueryReputation(query, expectedShouldBlock) {
-  return new Promise(resolve => {
-    function onComplete(aShouldBlock, aStatus) {
-      do_check_eq(Cr.NS_OK, aStatus);
-      do_check_eq(aShouldBlock, expectedShouldBlock);
-      resolve(true);
-    }
-    gAppRep.queryReputation(query, onComplete);
-  });
-}
-
-add_task(async function() {
-  // Wait for Safebrowsing local list updates to complete.
-  await waitForUpdates();
-});
-
-add_task(async function test_signature_whitelists() {
-  // We should never get to the remote server.
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             true);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/throw");
-
-  // Use BackgroundFileSaver to extract the signature on Windows.
-  let destFile = getTempFile(TEST_FILE_NAME_1);
-
-  let data = readFileToString("data/signed_win.exe");
-  let saver = new BackgroundFileSaverOutputStream();
-  let completionPromise = promiseSaverComplete(saver);
-  saver.enableSignatureInfo();
-  saver.setTarget(destFile, false);
-  await promiseCopyToSaver(data, saver, true);
-
-  saver.finish(Cr.NS_OK);
-  await completionPromise;
-
-  // Clean up.
-  destFile.remove(false);
-
-  // evil.com is not on the allowlist, but this binary is signed by an entity
-  // whose certificate information is on the allowlist.
-  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
-                                signatureInfo: saver.signatureInfo,
-                                fileSize: 12}, false);
-});
-
-add_task(async function test_blocked_binary() {
-  // We should reach the remote server for a verdict.
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             true);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  // evil.com should return a malware verdict from the remote server.
-  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
-                                suggestedFileName: "noop.bat",
-                                fileSize: 12}, true);
-});
-
-add_task(async function test_non_binary() {
-  // We should not reach the remote server for a verdict for non-binary files.
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             true);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/throw");
-  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
-                                suggestedFileName: "noop.txt",
-                                fileSize: 12}, false);
-});
-
-add_task(async function test_good_binary() {
-  // We should reach the remote server for a verdict.
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             true);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  // mozilla.com should return a not-guilty verdict from the remote server.
-  await promiseQueryReputation({sourceURI: createURI("http://mozilla.com"),
-                                suggestedFileName: "noop.bat",
-                                fileSize: 12}, false);
-});
-
-add_task(async function test_disabled() {
-  // Explicitly disable remote checks
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             false);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/throw");
-  let query = {sourceURI: createURI("http://example.com"),
-               suggestedFileName: "noop.bat",
-               fileSize: 12};
-  await new Promise(resolve => {
-    gAppRep.queryReputation(query,
-      function onComplete(aShouldBlock, aStatus) {
-        // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
-        do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
-        do_check_false(aShouldBlock);
-        resolve(true);
-      }
-    );
-  });
-});
-
-add_task(async function test_disabled_through_lists() {
-  Services.prefs.setBoolPref(remoteEnabledPref,
-                             false);
-  Services.prefs.setCharPref(appRepURLPref,
-                             "http://localhost:4444/download");
-  Services.prefs.setCharPref("urlclassifier.downloadBlockTable", "");
-  let query = {sourceURI: createURI("http://example.com"),
-               suggestedFileName: "noop.bat",
-               fileSize: 12};
-  await new Promise(resolve => {
-    gAppRep.queryReputation(query,
-      function onComplete(aShouldBlock, aStatus) {
-        // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
-        do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
-        do_check_false(aShouldBlock);
-        resolve(true);
-      }
-    );
-  });
-});
-add_task(async function test_teardown() {
-  gStillRunning = false;
-});
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -66,17 +66,17 @@ XPCOMUtils.defineLazyGetter(this, "gPare
   if ("@mozilla.org/parental-controls-service;1" in Cc) {
     return Cc["@mozilla.org/parental-controls-service;1"]
       .createInstance(Ci.nsIParentalControlsService);
   }
   return null;
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "gApplicationReputationService",
-           "@mozilla.org/downloads/application-reputation-service;1",
+           "@mozilla.org/reputationservice/application-reputation-service;1",
            Ci.nsIApplicationReputationService);
 
 XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
                                    "@mozilla.org/telephony/volume-service;1",
                                    "nsIVolumeService");
 
 // We have to use the gCombinedDownloadIntegration identifier because, in this
 // module only, the DownloadIntegration identifier refers to the base version.
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -48,16 +48,17 @@ DIRS += [
     'privatebrowsing',
     'processsingleton',
     'promiseworker',
     'prompts',
     'protobuf',
     'reader',
     'remotebrowserutils',
     'reflect',
+    'reputationservice',
     'resistfingerprinting',
     'securityreporter',
     'startup',
     'statusfilter',
     'telemetry',
     'thumbnails',
     'timermanager',
     'tooltiptext',
rename from toolkit/components/downloads/ApplicationReputation.cpp
rename to toolkit/components/reputationservice/ApplicationReputation.cpp
rename from toolkit/components/downloads/ApplicationReputation.h
rename to toolkit/components/reputationservice/ApplicationReputation.h
rename from toolkit/components/downloads/chromium/LICENSE
rename to toolkit/components/reputationservice/chromium/LICENSE
rename from toolkit/components/downloads/chromium/chrome/common/safe_browsing/csd.pb.cc
rename to toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.pb.cc
rename from toolkit/components/downloads/chromium/chrome/common/safe_browsing/csd.pb.h
rename to toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.pb.h
rename from toolkit/components/downloads/chromium/chrome/common/safe_browsing/csd.proto
rename to toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.proto
new file mode 100755
--- /dev/null
+++ b/toolkit/components/reputationservice/generate_csd.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+# A script to generate
+# chromium/chrome/common/safe_browsing/csd.pb.{cc,h} for use in
+# nsIApplicationReputationQuery. This script assumes you have
+# downloaded and installed the protocol buffer compiler.
+
+set -e
+
+if [ "${PROTOC_PATH:+set}" != "set" ]; 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://github.com/google/protobuf/releases"
+  exit 1
+fi
+
+if [ ! -f nsIApplicationReputation.idl ]; then
+    echo "You must run this script in the toolkit/components/reputationservice" >&2
+    echo "directory of the source tree." >&2
+    exit 1
+fi
+
+# Get the protocol buffer and compile it
+CSD_PROTO_URL="https://chromium.googlesource.com/chromium/src/+/master/chrome/common/safe_browsing/csd.proto?format=TEXT"
+CSD_PATH="chromium/chrome/common/safe_browsing"
+
+curl "$CSD_PROTO_URL" | base64 --decode > "$CSD_PATH"/csd.proto
+"$PROTOC_PATH" "$CSD_PATH"/csd.proto --cpp_out=.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/reputationservice/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; 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/.
+
+with Files('*'):
+    BUG_COMPONENT = ('Toolkit', 'Safe Browsing')
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
+XPIDL_SOURCES += [
+    'nsIApplicationReputation.idl',
+]
+
+XPIDL_MODULE = 'reputationservice'
+
+UNIFIED_SOURCES += [
+    'ApplicationReputation.cpp',
+    'chromium/chrome/common/safe_browsing/csd.pb.cc',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '../protobuf',
+    '/ipc/chromium/src',
+    'chromium',
+]
+
+DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
+DEFINES['GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER'] = True
+
+if CONFIG['GNU_CXX']:
+    CXXFLAGS += ['-Wno-shadow']
rename from toolkit/components/downloads/nsIApplicationReputation.idl
rename to toolkit/components/reputationservice/nsIApplicationReputation.idl
rename from toolkit/components/downloads/test/unit/.eslintrc.js
rename to toolkit/components/reputationservice/test/unit/.eslintrc.js
rename from toolkit/components/downloads/test/unit/data/block_digest.chunk
rename to toolkit/components/reputationservice/test/unit/data/block_digest.chunk
rename from toolkit/components/downloads/test/unit/data/digest.chunk
rename to toolkit/components/reputationservice/test/unit/data/digest.chunk
rename from toolkit/components/downloads/test/unit/data/signed_win.exe
rename to toolkit/components/reputationservice/test/unit/data/signed_win.exe
rename from toolkit/components/downloads/test/unit/head_download_manager.js
rename to toolkit/components/reputationservice/test/unit/head_download_manager.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/reputationservice/test/unit/test_app_rep.js
@@ -0,0 +1,346 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const gAppRep = Cc["@mozilla.org/reputationservice/application-reputation-service;1"].
+                  getService(Ci.nsIApplicationReputationService);
+var gHttpServ = null;
+var gTables = {};
+
+var ALLOW_LIST = 0;
+var BLOCK_LIST = 1;
+var NO_LIST = 2;
+
+var whitelistedURI = createURI("http://foo:bar@whitelisted.com/index.htm#junk");
+var exampleURI = createURI("http://user:password@example.com/i.html?foo=bar");
+var blocklistedURI = createURI("http://baz:qux@blocklisted.com?xyzzy");
+
+const appRepURLPref = "browser.safebrowsing.downloads.remote.url";
+
+function readFileToString(aFilename) {
+  let f = do_get_file(aFilename);
+  let stream = Cc["@mozilla.org/network/file-input-stream;1"]
+                 .createInstance(Ci.nsIFileInputStream);
+  stream.init(f, -1, 0, 0);
+  let buf = NetUtil.readInputStreamToString(stream, stream.available());
+  return buf;
+}
+
+// Registers a table for which to serve update chunks. Returns a promise that
+// resolves when that chunk has been downloaded.
+function registerTableUpdate(aTable, aFilename) {
+  // If we haven't been given an update for this table yet, add it to the map
+  if (!(aTable in gTables)) {
+    gTables[aTable] = [];
+  }
+
+  // The number of chunks associated with this table.
+  let numChunks = gTables[aTable].length + 1;
+  let redirectPath = "/" + aTable + "-" + numChunks;
+  let redirectUrl = "localhost:4444" + redirectPath;
+
+  // Store redirect url for that table so we can return it later when we
+  // process an update request.
+  gTables[aTable].push(redirectUrl);
+
+  gHttpServ.registerPathHandler(redirectPath, function(request, response) {
+    do_print("Mock safebrowsing server handling request for " + redirectPath);
+    let contents = readFileToString(aFilename);
+    do_print("Length of " + aFilename + ": " + contents.length);
+    response.setHeader("Content-Type",
+                       "application/vnd.google.safebrowsing-update", false);
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.bodyOutputStream.write(contents, contents.length);
+  });
+}
+
+add_task(async function test_setup() {
+  // Set up a local HTTP server to return bad verdicts.
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  // Ensure safebrowsing is enabled for this test, even if the app
+  // doesn't have it enabled.
+  Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true);
+  Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", true);
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled");
+    Services.prefs.clearUserPref("browser.safebrowsing.downloads.enabled");
+  });
+
+  // Set block and allow tables explicitly, since the allowlist is normally
+  // disabled on non-Windows platforms.
+  Services.prefs.setCharPref("urlclassifier.downloadBlockTable",
+                             "goog-badbinurl-shavar");
+  Services.prefs.setCharPref("urlclassifier.downloadAllowTable",
+                             "goog-downloadwhite-digest256");
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref("urlclassifier.downloadBlockTable");
+    Services.prefs.clearUserPref("urlclassifier.downloadAllowTable");
+  });
+
+  gHttpServ = new HttpServer();
+  gHttpServ.registerDirectory("/", do_get_cwd());
+  gHttpServ.registerPathHandler("/download", function(request, response) {
+    do_throw("This test should never make a remote lookup");
+  });
+  gHttpServ.start(4444);
+
+  do_register_cleanup(function() {
+    return (async function() {
+      await new Promise(resolve => {
+        gHttpServ.stop(resolve);
+      });
+    })();
+  });
+});
+
+function check_telemetry(aShouldBlockCount,
+                         aListCounts) {
+  let local = Cc["@mozilla.org/base/telemetry;1"]
+                .getService(Ci.nsITelemetry)
+                .getHistogramById("APPLICATION_REPUTATION_LOCAL")
+                .snapshot();
+  do_check_eq(local.counts[ALLOW_LIST], aListCounts[ALLOW_LIST],
+              "Allow list counts don't match");
+  do_check_eq(local.counts[BLOCK_LIST], aListCounts[BLOCK_LIST],
+              "Block list counts don't match");
+  do_check_eq(local.counts[NO_LIST], aListCounts[NO_LIST],
+              "No list counts don't match");
+
+  let shouldBlock = Cc["@mozilla.org/base/telemetry;1"]
+                .getService(Ci.nsITelemetry)
+                .getHistogramById("APPLICATION_REPUTATION_SHOULD_BLOCK")
+                .snapshot();
+  // SHOULD_BLOCK = true
+  do_check_eq(shouldBlock.counts[1], aShouldBlockCount);
+}
+
+function get_telemetry_counts() {
+  let local = Cc["@mozilla.org/base/telemetry;1"]
+                .getService(Ci.nsITelemetry)
+                .getHistogramById("APPLICATION_REPUTATION_LOCAL")
+                .snapshot();
+  let shouldBlock = Cc["@mozilla.org/base/telemetry;1"]
+                .getService(Ci.nsITelemetry)
+                .getHistogramById("APPLICATION_REPUTATION_SHOULD_BLOCK")
+                .snapshot();
+  return { shouldBlock: shouldBlock.counts[1],
+           listCounts: local.counts };
+}
+
+add_test(function test_nullSourceURI() {
+  let counts = get_telemetry_counts();
+  gAppRep.queryReputation({
+    // No source URI
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_ERROR_UNEXPECTED, aStatus);
+    do_check_false(aShouldBlock);
+    check_telemetry(counts.shouldBlock, counts.listCounts);
+    run_next_test();
+  });
+});
+
+add_test(function test_nullCallback() {
+  let counts = get_telemetry_counts();
+  try {
+    gAppRep.queryReputation({
+      sourceURI: createURI("http://example.com"),
+      fileSize: 12,
+    }, null);
+    do_throw("Callback cannot be null");
+  } catch (ex) {
+    if (ex.result != Cr.NS_ERROR_INVALID_POINTER)
+      throw ex;
+    // We don't even increment the count here, because there's no callback.
+    check_telemetry(counts.shouldBlock, counts.listCounts);
+    run_next_test();
+  }
+});
+
+// Set up the local whitelist.
+add_test(function test_local_list() {
+  // Construct a response with redirect urls.
+  function processUpdateRequest() {
+    let response = "n:1000\n";
+    for (let table in gTables) {
+      response += "i:" + table + "\n";
+      for (let i = 0; i < gTables[table].length; ++i) {
+        response += "u:" + gTables[table][i] + "\n";
+      }
+    }
+    do_print("Returning update response: " + response);
+    return response;
+  }
+  gHttpServ.registerPathHandler("/downloads", function(request, response) {
+    let blob = processUpdateRequest();
+    response.setHeader("Content-Type",
+                       "application/vnd.google.safebrowsing-update", false);
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.bodyOutputStream.write(blob, blob.length);
+  });
+
+  let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
+    .getService(Ci.nsIUrlClassifierStreamUpdater);
+
+  // Load up some update chunks for the safebrowsing server to serve.
+  // This chunk contains the hash of blocklisted.com/.
+  registerTableUpdate("goog-badbinurl-shavar", "data/block_digest.chunk");
+  // This chunk contains the hash of whitelisted.com/.
+  registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk");
+
+  // Download some updates, and don't continue until the downloads are done.
+  function updateSuccess(aEvent) {
+    // Timeout of n:1000 is constructed in processUpdateRequest above and
+    // passed back in the callback in nsIUrlClassifierStreamUpdater on success.
+    do_check_eq("1000", aEvent);
+    do_print("All data processed");
+    run_next_test();
+  }
+  // Just throw if we ever get an update or download error.
+  function handleError(aEvent) {
+    do_throw("We didn't download or update correctly: " + aEvent);
+  }
+  streamUpdater.downloadUpdates(
+    "goog-downloadwhite-digest256,goog-badbinurl-shavar",
+    "goog-downloadwhite-digest256,goog-badbinurl-shavar;\n",
+    true, // isPostRequest.
+    "http://localhost:4444/downloads",
+    updateSuccess, handleError, handleError);
+});
+
+add_test(function test_unlisted() {
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  let counts = get_telemetry_counts();
+  let listCounts = counts.listCounts;
+  listCounts[NO_LIST]++;
+  gAppRep.queryReputation({
+    sourceURI: exampleURI,
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_false(aShouldBlock);
+    check_telemetry(counts.shouldBlock, listCounts);
+    run_next_test();
+  });
+});
+
+add_test(function test_non_uri() {
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  let counts = get_telemetry_counts();
+  let listCounts = counts.listCounts;
+  // No listcount is incremented, since the sourceURI is not an nsIURL
+  let source = NetUtil.newURI("data:application/octet-stream,ABC");
+  do_check_false(source instanceof Ci.nsIURL);
+  gAppRep.queryReputation({
+    sourceURI: source,
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_false(aShouldBlock);
+    check_telemetry(counts.shouldBlock, listCounts);
+    run_next_test();
+  });
+});
+
+add_test(function test_local_blacklist() {
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  let counts = get_telemetry_counts();
+  let listCounts = counts.listCounts;
+  listCounts[BLOCK_LIST]++;
+  gAppRep.queryReputation({
+    sourceURI: blocklistedURI,
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_true(aShouldBlock);
+    check_telemetry(counts.shouldBlock + 1, listCounts);
+    run_next_test();
+  });
+});
+
+add_test(function test_referer_blacklist() {
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  let counts = get_telemetry_counts();
+  let listCounts = counts.listCounts;
+  listCounts[BLOCK_LIST]++;
+  gAppRep.queryReputation({
+    sourceURI: exampleURI,
+    referrerURI: blocklistedURI,
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_true(aShouldBlock);
+    check_telemetry(counts.shouldBlock + 1, listCounts);
+    run_next_test();
+  });
+});
+
+add_test(function test_blocklist_trumps_allowlist() {
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  let counts = get_telemetry_counts();
+  let listCounts = counts.listCounts;
+  listCounts[BLOCK_LIST]++;
+  gAppRep.queryReputation({
+    sourceURI: whitelistedURI,
+    referrerURI: blocklistedURI,
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_true(aShouldBlock);
+    check_telemetry(counts.shouldBlock + 1, listCounts);
+    run_next_test();
+  });
+});
+
+add_test(function test_redirect_on_blocklist() {
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  let counts = get_telemetry_counts();
+  let listCounts = counts.listCounts;
+  listCounts[BLOCK_LIST]++;
+  listCounts[ALLOW_LIST]++;
+  let secman = Services.scriptSecurityManager;
+  let badRedirects = Cc["@mozilla.org/array;1"]
+                       .createInstance(Ci.nsIMutableArray);
+
+  let redirect1 = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIRedirectHistoryEntry]),
+    principal: secman.createCodebasePrincipal(exampleURI, {}),
+  };
+  badRedirects.appendElement(redirect1);
+
+  let redirect2 = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIRedirectHistoryEntry]),
+    principal: secman.createCodebasePrincipal(blocklistedURI, {}),
+  };
+  badRedirects.appendElement(redirect2);
+
+  let redirect3 = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIRedirectHistoryEntry]),
+    principal: secman.createCodebasePrincipal(whitelistedURI, {}),
+  };
+  badRedirects.appendElement(redirect3);
+
+  gAppRep.queryReputation({
+    sourceURI: whitelistedURI,
+    referrerURI: exampleURI,
+    redirects: badRedirects,
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_true(aShouldBlock);
+    check_telemetry(counts.shouldBlock + 1, listCounts);
+    run_next_test();
+  });
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/reputationservice/test/unit/test_app_rep_maclinux.js
@@ -0,0 +1,289 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Globals
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+
+const gAppRep = Cc["@mozilla.org/reputationservice/application-reputation-service;1"].
+                  getService(Ci.nsIApplicationReputationService);
+var gStillRunning = true;
+var gTables = {};
+var gHttpServer = null;
+
+const appRepURLPref = "browser.safebrowsing.downloads.remote.url";
+const remoteEnabledPref = "browser.safebrowsing.downloads.remote.enabled";
+
+function readFileToString(aFilename) {
+  let f = do_get_file(aFilename);
+  let stream = Cc["@mozilla.org/network/file-input-stream;1"]
+                 .createInstance(Ci.nsIFileInputStream);
+  stream.init(f, -1, 0, 0);
+  let buf = NetUtil.readInputStreamToString(stream, stream.available());
+  return buf;
+}
+
+function registerTableUpdate(aTable, aFilename) {
+  // If we haven't been given an update for this table yet, add it to the map
+  if (!(aTable in gTables)) {
+    gTables[aTable] = [];
+  }
+
+  // The number of chunks associated with this table.
+  let numChunks = gTables[aTable].length + 1;
+  let redirectPath = "/" + aTable + "-" + numChunks;
+  let redirectUrl = "localhost:4444" + redirectPath;
+
+  // Store redirect url for that table so we can return it later when we
+  // process an update request.
+  gTables[aTable].push(redirectUrl);
+
+  gHttpServer.registerPathHandler(redirectPath, function(request, response) {
+    do_print("Mock safebrowsing server handling request for " + redirectPath);
+    let contents = readFileToString(aFilename);
+    do_print("Length of " + aFilename + ": " + contents.length);
+    response.setHeader("Content-Type",
+                       "application/vnd.google.safebrowsing-update", false);
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.bodyOutputStream.write(contents, contents.length);
+  });
+}
+
+// Tests
+
+add_task(function test_setup() {
+  // Wait 10 minutes, that is half of the external xpcshell timeout.
+  do_timeout(10 * 60 * 1000, function() {
+    if (gStillRunning) {
+      do_throw("Test timed out.");
+    }
+  });
+  // Set up a local HTTP server to return bad verdicts.
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  // Ensure safebrowsing is enabled for this test, even if the app
+  // doesn't have it enabled.
+  Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true);
+  Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", true);
+  // Set block table explicitly, no need for the allow table though
+  Services.prefs.setCharPref("urlclassifier.downloadBlockTable",
+                             "goog-badbinurl-shavar");
+  // SendRemoteQueryInternal needs locale preference.
+  let locale = Services.prefs.getCharPref("general.useragent.locale");
+  Services.prefs.setCharPref("general.useragent.locale", "en-US");
+
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled");
+    Services.prefs.clearUserPref("browser.safebrowsing.downloads.enabled");
+    Services.prefs.clearUserPref("urlclassifier.downloadBlockTable");
+    Services.prefs.setCharPref("general.useragent.locale", locale);
+  });
+
+  gHttpServer = new HttpServer();
+  gHttpServer.registerDirectory("/", do_get_cwd());
+
+  function createVerdict(aShouldBlock) {
+    // We can't programmatically create a protocol buffer here, so just
+    // hardcode some already serialized ones.
+    let blob = String.fromCharCode(parseInt(0x08, 16));
+    if (aShouldBlock) {
+      // A safe_browsing::ClientDownloadRequest with a DANGEROUS verdict
+      blob += String.fromCharCode(parseInt(0x01, 16));
+    } else {
+      // A safe_browsing::ClientDownloadRequest with a SAFE verdict
+      blob += String.fromCharCode(parseInt(0x00, 16));
+    }
+    return blob;
+  }
+
+  gHttpServer.registerPathHandler("/throw", function(request, response) {
+    do_throw("We shouldn't be getting here");
+  });
+
+  gHttpServer.registerPathHandler("/download", function(request, response) {
+    do_print("Querying remote server for verdict");
+    response.setHeader("Content-Type", "application/octet-stream", false);
+    let buf = NetUtil.readInputStreamToString(
+      request.bodyInputStream,
+      request.bodyInputStream.available());
+    do_print("Request length: " + buf.length);
+    // A garbage response. By default this produces NS_CANNOT_CONVERT_DATA as
+    // the callback status.
+    let blob = "this is not a serialized protocol buffer (the length doesn't match our hard-coded values)";
+    // We can't actually parse the protocol buffer here, so just switch on the
+    // length instead of inspecting the contents.
+    if (buf.length == 67) {
+      // evil.com
+      blob = createVerdict(true);
+    } else if (buf.length == 73) {
+      // mozilla.com
+      blob = createVerdict(false);
+    }
+    response.bodyOutputStream.write(blob, blob.length);
+  });
+
+  gHttpServer.start(4444);
+
+  do_register_cleanup(function() {
+    return (async function() {
+      await new Promise(resolve => {
+        gHttpServer.stop(resolve);
+      });
+    })();
+  });
+});
+
+// Construct a response with redirect urls.
+function processUpdateRequest() {
+  let response = "n:1000\n";
+  for (let table in gTables) {
+    response += "i:" + table + "\n";
+    for (let i = 0; i < gTables[table].length; ++i) {
+      response += "u:" + gTables[table][i] + "\n";
+    }
+  }
+  do_print("Returning update response: " + response);
+  return response;
+}
+
+// Set up the local whitelist.
+function waitForUpdates() {
+  return new Promise((resolve, reject) => {
+    gHttpServer.registerPathHandler("/downloads", function(request, response) {
+      let blob = processUpdateRequest();
+      response.setHeader("Content-Type",
+                         "application/vnd.google.safebrowsing-update", false);
+      response.setStatusLine(request.httpVersion, 200, "OK");
+      response.bodyOutputStream.write(blob, blob.length);
+    });
+
+    let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
+      .getService(Ci.nsIUrlClassifierStreamUpdater);
+
+    // Load up some update chunks for the safebrowsing server to serve. This
+    // particular chunk contains the hash of whitelisted.com/ and
+    // sb-ssl.google.com/safebrowsing/csd/certificate/.
+    registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk");
+
+    // Resolve the promise once processing the updates is complete.
+    function updateSuccess(aEvent) {
+      // Timeout of n:1000 is constructed in processUpdateRequest above and
+      // passed back in the callback in nsIUrlClassifierStreamUpdater on success.
+      do_check_eq("1000", aEvent);
+      do_print("All data processed");
+      resolve(true);
+    }
+    // Just throw if we ever get an update or download error.
+    function handleError(aEvent) {
+      do_throw("We didn't download or update correctly: " + aEvent);
+      reject();
+    }
+    streamUpdater.downloadUpdates(
+      "goog-downloadwhite-digest256",
+      "goog-downloadwhite-digest256;\n",
+      true,
+      "http://localhost:4444/downloads",
+      updateSuccess, handleError, handleError);
+  });
+}
+
+function promiseQueryReputation(query, expectedShouldBlock) {
+  return new Promise(resolve => {
+    function onComplete(aShouldBlock, aStatus) {
+      do_check_eq(Cr.NS_OK, aStatus);
+      do_check_eq(aShouldBlock, expectedShouldBlock);
+      resolve(true);
+    }
+    gAppRep.queryReputation(query, onComplete);
+  });
+}
+
+add_task(async function() {
+  // Wait for Safebrowsing local list updates to complete.
+  await waitForUpdates();
+});
+
+add_task(async function test_blocked_binary() {
+  // We should reach the remote server for a verdict.
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             true);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  // evil.com should return a malware verdict from the remote server.
+  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                suggestedFileName: "noop.bat",
+                                fileSize: 12}, true);
+});
+
+add_task(async function test_non_binary() {
+  // We should not reach the remote server for a verdict for non-binary files.
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             true);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/throw");
+  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                suggestedFileName: "noop.txt",
+                                fileSize: 12}, false);
+});
+
+add_task(async function test_good_binary() {
+  // We should reach the remote server for a verdict.
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             true);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  // mozilla.com should return a not-guilty verdict from the remote server.
+  await promiseQueryReputation({sourceURI: createURI("http://mozilla.com"),
+                                suggestedFileName: "noop.bat",
+                                fileSize: 12}, false);
+});
+
+add_task(async function test_disabled() {
+  // Explicitly disable remote checks
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             false);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/throw");
+  let query = {sourceURI: createURI("http://example.com"),
+               suggestedFileName: "noop.bat",
+               fileSize: 12};
+  await new Promise(resolve => {
+    gAppRep.queryReputation(query,
+      function onComplete(aShouldBlock, aStatus) {
+        // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
+        do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
+        do_check_false(aShouldBlock);
+        resolve(true);
+      }
+    );
+  });
+});
+
+add_task(async function test_disabled_through_lists() {
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             false);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  Services.prefs.setCharPref("urlclassifier.downloadBlockTable", "");
+  let query = {sourceURI: createURI("http://example.com"),
+               suggestedFileName: "noop.bat",
+               fileSize: 12};
+  await new Promise(resolve => {
+    gAppRep.queryReputation(query,
+      function onComplete(aShouldBlock, aStatus) {
+        // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
+        do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
+        do_check_false(aShouldBlock);
+        resolve(true);
+      }
+    );
+  });
+});
+add_task(async function test_teardown() {
+  gStillRunning = false;
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/reputationservice/test/unit/test_app_rep_windows.js
@@ -0,0 +1,421 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests signature extraction using Windows Authenticode APIs of
+ * downloaded files.
+ */
+
+// Globals
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+
+const BackgroundFileSaverOutputStream = Components.Constructor(
+      "@mozilla.org/network/background-file-saver;1?mode=outputstream",
+      "nsIBackgroundFileSaver");
+
+const StringInputStream = Components.Constructor(
+      "@mozilla.org/io/string-input-stream;1",
+      "nsIStringInputStream",
+      "setData");
+
+const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt";
+
+const gAppRep = Cc["@mozilla.org/reputationservice/application-reputation-service;1"].
+                  getService(Ci.nsIApplicationReputationService);
+var gStillRunning = true;
+var gTables = {};
+var gHttpServer = null;
+
+const appRepURLPref = "browser.safebrowsing.downloads.remote.url";
+const remoteEnabledPref = "browser.safebrowsing.downloads.remote.enabled";
+
+/**
+ * Returns a reference to a temporary file.  If the file is then created, it
+ * will be removed when tests in this file finish.
+ */
+function getTempFile(aLeafName) {
+  let file = FileUtils.getFile("TmpD", [aLeafName]);
+  do_register_cleanup(function GTF_cleanup() {
+    if (file.exists()) {
+      file.remove(false);
+    }
+  });
+  return file;
+}
+
+function readFileToString(aFilename) {
+  let f = do_get_file(aFilename);
+  let stream = Cc["@mozilla.org/network/file-input-stream;1"]
+                 .createInstance(Ci.nsIFileInputStream);
+  stream.init(f, -1, 0, 0);
+  let buf = NetUtil.readInputStreamToString(stream, stream.available());
+  return buf;
+}
+
+/**
+ * Waits for the given saver object to complete.
+ *
+ * @param aSaver
+ *        The saver, with the output stream or a stream listener implementation.
+ * @param aOnTargetChangeFn
+ *        Optional callback invoked with the target file name when it changes.
+ *
+ * @return {Promise}
+ * @resolves When onSaveComplete is called with a success code.
+ * @rejects With an exception, if onSaveComplete is called with a failure code.
+ */
+function promiseSaverComplete(aSaver, aOnTargetChangeFn) {
+  return new Promise((resolve, reject) => {
+    aSaver.observer = {
+      onTargetChange: function BFSO_onSaveComplete(unused, aTarget) {
+        if (aOnTargetChangeFn) {
+          aOnTargetChangeFn(aTarget);
+        }
+      },
+      onSaveComplete: function BFSO_onSaveComplete(unused, aStatus) {
+        if (Components.isSuccessCode(aStatus)) {
+          resolve();
+        } else {
+          reject(new Components.Exception("Saver failed.", aStatus));
+        }
+      },
+    };
+  });
+}
+
+/**
+ * Feeds a string to a BackgroundFileSaverOutputStream.
+ *
+ * @param aSourceString
+ *        The source data to copy.
+ * @param aSaverOutputStream
+ *        The BackgroundFileSaverOutputStream to feed.
+ * @param aCloseWhenDone
+ *        If true, the output stream will be closed when the copy finishes.
+ *
+ * @return {Promise}
+ * @resolves When the copy completes with a success code.
+ * @rejects With an exception, if the copy fails.
+ */
+function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) {
+  return new Promise((resolve, reject) => {
+    let inputStream = new StringInputStream(aSourceString, aSourceString.length);
+    let copier = Cc["@mozilla.org/network/async-stream-copier;1"]
+                 .createInstance(Ci.nsIAsyncStreamCopier);
+    copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true,
+                aCloseWhenDone);
+    copier.asyncCopy({
+      onStartRequest() { },
+      onStopRequest(aRequest, aContext, aStatusCode) {
+        if (Components.isSuccessCode(aStatusCode)) {
+          resolve();
+        } else {
+          reject(new Components.Exception(aStatusCode));
+        }
+      },
+    }, null);
+  });
+}
+
+// Registers a table for which to serve update chunks.
+function registerTableUpdate(aTable, aFilename) {
+  // If we haven't been given an update for this table yet, add it to the map
+  if (!(aTable in gTables)) {
+    gTables[aTable] = [];
+  }
+
+  // The number of chunks associated with this table.
+  let numChunks = gTables[aTable].length + 1;
+  let redirectPath = "/" + aTable + "-" + numChunks;
+  let redirectUrl = "localhost:4444" + redirectPath;
+
+  // Store redirect url for that table so we can return it later when we
+  // process an update request.
+  gTables[aTable].push(redirectUrl);
+
+  gHttpServer.registerPathHandler(redirectPath, function(request, response) {
+    do_print("Mock safebrowsing server handling request for " + redirectPath);
+    let contents = readFileToString(aFilename);
+    do_print("Length of " + aFilename + ": " + contents.length);
+    response.setHeader("Content-Type",
+                       "application/vnd.google.safebrowsing-update", false);
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.bodyOutputStream.write(contents, contents.length);
+  });
+}
+
+// Tests
+
+add_task(async function test_setup() {
+  // Wait 10 minutes, that is half of the external xpcshell timeout.
+  do_timeout(10 * 60 * 1000, function() {
+    if (gStillRunning) {
+      do_throw("Test timed out.");
+    }
+  });
+  // Set up a local HTTP server to return bad verdicts.
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  // Ensure safebrowsing is enabled for this test, even if the app
+  // doesn't have it enabled.
+  Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true);
+  Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", true);
+  // Set block and allow tables explicitly, since the allowlist is normally
+  // disabled on comm-central.
+  Services.prefs.setCharPref("urlclassifier.downloadBlockTable",
+                             "goog-badbinurl-shavar");
+  Services.prefs.setCharPref("urlclassifier.downloadAllowTable",
+                             "goog-downloadwhite-digest256");
+  // SendRemoteQueryInternal needs locale preference.
+  let locale = Services.prefs.getCharPref("general.useragent.locale");
+  Services.prefs.setCharPref("general.useragent.locale", "en-US");
+
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled");
+    Services.prefs.clearUserPref("browser.safebrowsing.downloads.enabled");
+    Services.prefs.clearUserPref("urlclassifier.downloadBlockTable");
+    Services.prefs.clearUserPref("urlclassifier.downloadAllowTable");
+    Services.prefs.setCharPref("general.useragent.locale", locale);
+  });
+
+  gHttpServer = new HttpServer();
+  gHttpServer.registerDirectory("/", do_get_cwd());
+
+  function createVerdict(aShouldBlock) {
+    // We can't programmatically create a protocol buffer here, so just
+    // hardcode some already serialized ones.
+    let blob = String.fromCharCode(parseInt(0x08, 16));
+    if (aShouldBlock) {
+      // A safe_browsing::ClientDownloadRequest with a DANGEROUS verdict
+      blob += String.fromCharCode(parseInt(0x01, 16));
+    } else {
+      // A safe_browsing::ClientDownloadRequest with a SAFE verdict
+      blob += String.fromCharCode(parseInt(0x00, 16));
+    }
+    return blob;
+  }
+
+  gHttpServer.registerPathHandler("/throw", function(request, response) {
+    do_throw("We shouldn't be getting here");
+  });
+
+  gHttpServer.registerPathHandler("/download", function(request, response) {
+    do_print("Querying remote server for verdict");
+    response.setHeader("Content-Type", "application/octet-stream", false);
+    let buf = NetUtil.readInputStreamToString(
+      request.bodyInputStream,
+      request.bodyInputStream.available());
+    do_print("Request length: " + buf.length);
+    // A garbage response. By default this produces NS_CANNOT_CONVERT_DATA as
+    // the callback status.
+    let blob = "this is not a serialized protocol buffer (the length doesn't match our hard-coded values)";
+    // We can't actually parse the protocol buffer here, so just switch on the
+    // length instead of inspecting the contents.
+    if (buf.length == 67) {
+      // evil.com
+      blob = createVerdict(true);
+    } else if (buf.length == 73) {
+      // mozilla.com
+      blob = createVerdict(false);
+    }
+    response.bodyOutputStream.write(blob, blob.length);
+  });
+
+  gHttpServer.start(4444);
+
+  do_register_cleanup(function() {
+    return (async function() {
+      await new Promise(resolve => {
+        gHttpServer.stop(resolve);
+      });
+    })();
+  });
+});
+
+// Construct a response with redirect urls.
+function processUpdateRequest() {
+  let response = "n:1000\n";
+  for (let table in gTables) {
+    response += "i:" + table + "\n";
+    for (let i = 0; i < gTables[table].length; ++i) {
+      response += "u:" + gTables[table][i] + "\n";
+    }
+  }
+  do_print("Returning update response: " + response);
+  return response;
+}
+
+// Set up the local whitelist.
+function waitForUpdates() {
+  return new Promise((resolve, reject) => {
+    gHttpServer.registerPathHandler("/downloads", function(request, response) {
+      let blob = processUpdateRequest();
+      response.setHeader("Content-Type",
+                         "application/vnd.google.safebrowsing-update", false);
+      response.setStatusLine(request.httpVersion, 200, "OK");
+      response.bodyOutputStream.write(blob, blob.length);
+    });
+
+    let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
+      .getService(Ci.nsIUrlClassifierStreamUpdater);
+
+    // Load up some update chunks for the safebrowsing server to serve. This
+    // particular chunk contains the hash of whitelisted.com/ and
+    // sb-ssl.google.com/safebrowsing/csd/certificate/.
+    registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk");
+
+    // Resolve the promise once processing the updates is complete.
+    function updateSuccess(aEvent) {
+      // Timeout of n:1000 is constructed in processUpdateRequest above and
+      // passed back in the callback in nsIUrlClassifierStreamUpdater on success.
+      do_check_eq("1000", aEvent);
+      do_print("All data processed");
+      resolve(true);
+    }
+    // Just throw if we ever get an update or download error.
+    function handleError(aEvent) {
+      do_throw("We didn't download or update correctly: " + aEvent);
+      reject();
+    }
+    streamUpdater.downloadUpdates(
+      "goog-downloadwhite-digest256",
+      "goog-downloadwhite-digest256;\n",
+      true,
+      "http://localhost:4444/downloads",
+      updateSuccess, handleError, handleError);
+  });
+}
+
+function promiseQueryReputation(query, expectedShouldBlock) {
+  return new Promise(resolve => {
+    function onComplete(aShouldBlock, aStatus) {
+      do_check_eq(Cr.NS_OK, aStatus);
+      do_check_eq(aShouldBlock, expectedShouldBlock);
+      resolve(true);
+    }
+    gAppRep.queryReputation(query, onComplete);
+  });
+}
+
+add_task(async function() {
+  // Wait for Safebrowsing local list updates to complete.
+  await waitForUpdates();
+});
+
+add_task(async function test_signature_whitelists() {
+  // We should never get to the remote server.
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             true);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/throw");
+
+  // Use BackgroundFileSaver to extract the signature on Windows.
+  let destFile = getTempFile(TEST_FILE_NAME_1);
+
+  let data = readFileToString("data/signed_win.exe");
+  let saver = new BackgroundFileSaverOutputStream();
+  let completionPromise = promiseSaverComplete(saver);
+  saver.enableSignatureInfo();
+  saver.setTarget(destFile, false);
+  await promiseCopyToSaver(data, saver, true);
+
+  saver.finish(Cr.NS_OK);
+  await completionPromise;
+
+  // Clean up.
+  destFile.remove(false);
+
+  // evil.com is not on the allowlist, but this binary is signed by an entity
+  // whose certificate information is on the allowlist.
+  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                signatureInfo: saver.signatureInfo,
+                                fileSize: 12}, false);
+});
+
+add_task(async function test_blocked_binary() {
+  // We should reach the remote server for a verdict.
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             true);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  // evil.com should return a malware verdict from the remote server.
+  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                suggestedFileName: "noop.bat",
+                                fileSize: 12}, true);
+});
+
+add_task(async function test_non_binary() {
+  // We should not reach the remote server for a verdict for non-binary files.
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             true);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/throw");
+  await promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                suggestedFileName: "noop.txt",
+                                fileSize: 12}, false);
+});
+
+add_task(async function test_good_binary() {
+  // We should reach the remote server for a verdict.
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             true);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  // mozilla.com should return a not-guilty verdict from the remote server.
+  await promiseQueryReputation({sourceURI: createURI("http://mozilla.com"),
+                                suggestedFileName: "noop.bat",
+                                fileSize: 12}, false);
+});
+
+add_task(async function test_disabled() {
+  // Explicitly disable remote checks
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             false);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/throw");
+  let query = {sourceURI: createURI("http://example.com"),
+               suggestedFileName: "noop.bat",
+               fileSize: 12};
+  await new Promise(resolve => {
+    gAppRep.queryReputation(query,
+      function onComplete(aShouldBlock, aStatus) {
+        // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
+        do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
+        do_check_false(aShouldBlock);
+        resolve(true);
+      }
+    );
+  });
+});
+
+add_task(async function test_disabled_through_lists() {
+  Services.prefs.setBoolPref(remoteEnabledPref,
+                             false);
+  Services.prefs.setCharPref(appRepURLPref,
+                             "http://localhost:4444/download");
+  Services.prefs.setCharPref("urlclassifier.downloadBlockTable", "");
+  let query = {sourceURI: createURI("http://example.com"),
+               suggestedFileName: "noop.bat",
+               fileSize: 12};
+  await new Promise(resolve => {
+    gAppRep.queryReputation(query,
+      function onComplete(aShouldBlock, aStatus) {
+        // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled
+        do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus);
+        do_check_false(aShouldBlock);
+        resolve(true);
+      }
+    );
+  });
+});
+add_task(async function test_teardown() {
+  gStillRunning = false;
+});
rename from toolkit/components/downloads/test/unit/xpcshell.ini
rename to toolkit/components/reputationservice/test/unit/xpcshell.ini