|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 Cu.import('resource://gre/modules/NetUtil.jsm'); |
|
8 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
9 |
|
10 const gAppRep = Cc["@mozilla.org/downloads/application-reputation-service;1"]. |
|
11 getService(Ci.nsIApplicationReputationService); |
|
12 let gHttpServ = null; |
|
13 let gTables = {}; |
|
14 |
|
15 let ALLOW_LIST = 0; |
|
16 let BLOCK_LIST = 1; |
|
17 let NO_LIST = 2; |
|
18 |
|
19 function readFileToString(aFilename) { |
|
20 let f = do_get_file(aFilename); |
|
21 let stream = Cc["@mozilla.org/network/file-input-stream;1"] |
|
22 .createInstance(Ci.nsIFileInputStream); |
|
23 stream.init(f, -1, 0, 0); |
|
24 let buf = NetUtil.readInputStreamToString(stream, stream.available()); |
|
25 return buf; |
|
26 } |
|
27 |
|
28 // Registers a table for which to serve update chunks. Returns a promise that |
|
29 // resolves when that chunk has been downloaded. |
|
30 function registerTableUpdate(aTable, aFilename) { |
|
31 // If we haven't been given an update for this table yet, add it to the map |
|
32 if (!(aTable in gTables)) { |
|
33 gTables[aTable] = []; |
|
34 } |
|
35 |
|
36 // The number of chunks associated with this table. |
|
37 let numChunks = gTables[aTable].length + 1; |
|
38 let redirectPath = "/" + aTable + "-" + numChunks; |
|
39 let redirectUrl = "localhost:4444" + redirectPath; |
|
40 |
|
41 // Store redirect url for that table so we can return it later when we |
|
42 // process an update request. |
|
43 gTables[aTable].push(redirectUrl); |
|
44 |
|
45 gHttpServ.registerPathHandler(redirectPath, function(request, response) { |
|
46 do_print("Mock safebrowsing server handling request for " + redirectPath); |
|
47 let contents = readFileToString(aFilename); |
|
48 do_print("Length of " + aFilename + ": " + contents.length); |
|
49 response.setHeader("Content-Type", |
|
50 "application/vnd.google.safebrowsing-update", false); |
|
51 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
52 response.bodyOutputStream.write(contents, contents.length); |
|
53 }); |
|
54 } |
|
55 |
|
56 function run_test() { |
|
57 // Set up a local HTTP server to return bad verdicts. |
|
58 Services.prefs.setCharPref("browser.safebrowsing.appRepURL", |
|
59 "http://localhost:4444/download"); |
|
60 // Ensure safebrowsing is enabled for this test, even if the app |
|
61 // doesn't have it enabled. |
|
62 Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true); |
|
63 do_register_cleanup(function() { |
|
64 Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled"); |
|
65 }); |
|
66 |
|
67 // Set download_block_table explicitly. |
|
68 Services.prefs.setCharPref("urlclassifier.downloadBlockTable", |
|
69 "goog-badbinurl-shavar"); |
|
70 do_register_cleanup(function() { |
|
71 Services.prefs.clearUserPref("urlclassifier.downloadBlockTable"); |
|
72 }); |
|
73 |
|
74 gHttpServ = new HttpServer(); |
|
75 gHttpServ.registerDirectory("/", do_get_cwd()); |
|
76 gHttpServ.registerPathHandler("/download", function(request, response) { |
|
77 do_throw("This test should never make a remote lookup"); |
|
78 }); |
|
79 gHttpServ.start(4444); |
|
80 |
|
81 run_next_test(); |
|
82 } |
|
83 |
|
84 function check_telemetry(aCount, |
|
85 aShouldBlockCount, |
|
86 aListCounts) { |
|
87 let count = Cc["@mozilla.org/base/telemetry;1"] |
|
88 .getService(Ci.nsITelemetry) |
|
89 .getHistogramById("APPLICATION_REPUTATION_COUNT") |
|
90 .snapshot(); |
|
91 do_check_eq(count.counts[1], aCount); |
|
92 let local = Cc["@mozilla.org/base/telemetry;1"] |
|
93 .getService(Ci.nsITelemetry) |
|
94 .getHistogramById("APPLICATION_REPUTATION_LOCAL") |
|
95 .snapshot(); |
|
96 do_check_eq(local.counts[ALLOW_LIST], aListCounts[ALLOW_LIST]); |
|
97 do_check_eq(local.counts[BLOCK_LIST], aListCounts[BLOCK_LIST]); |
|
98 do_check_eq(local.counts[NO_LIST], aListCounts[NO_LIST]); |
|
99 let shouldBlock = Cc["@mozilla.org/base/telemetry;1"] |
|
100 .getService(Ci.nsITelemetry) |
|
101 .getHistogramById("APPLICATION_REPUTATION_SHOULD_BLOCK") |
|
102 .snapshot(); |
|
103 // SHOULD_BLOCK = true |
|
104 do_check_eq(shouldBlock.counts[1], aShouldBlockCount); |
|
105 // Sanity check that SHOULD_BLOCK total adds up to the COUNT. |
|
106 do_check_eq(shouldBlock.counts[0] + shouldBlock.counts[1], aCount); |
|
107 } |
|
108 |
|
109 function get_telemetry_counts() { |
|
110 let count = Cc["@mozilla.org/base/telemetry;1"] |
|
111 .getService(Ci.nsITelemetry) |
|
112 .getHistogramById("APPLICATION_REPUTATION_COUNT") |
|
113 .snapshot(); |
|
114 let local = Cc["@mozilla.org/base/telemetry;1"] |
|
115 .getService(Ci.nsITelemetry) |
|
116 .getHistogramById("APPLICATION_REPUTATION_LOCAL") |
|
117 .snapshot(); |
|
118 let shouldBlock = Cc["@mozilla.org/base/telemetry;1"] |
|
119 .getService(Ci.nsITelemetry) |
|
120 .getHistogramById("APPLICATION_REPUTATION_SHOULD_BLOCK") |
|
121 .snapshot(); |
|
122 return { total: count.counts[1], |
|
123 shouldBlock: shouldBlock.counts[1], |
|
124 listCounts: local.counts }; |
|
125 } |
|
126 |
|
127 add_test(function test_nullSourceURI() { |
|
128 let counts = get_telemetry_counts(); |
|
129 gAppRep.queryReputation({ |
|
130 // No source URI |
|
131 fileSize: 12, |
|
132 }, function onComplete(aShouldBlock, aStatus) { |
|
133 do_check_eq(Cr.NS_ERROR_UNEXPECTED, aStatus); |
|
134 do_check_false(aShouldBlock); |
|
135 check_telemetry(counts.total + 1, counts.shouldBlock, counts.listCounts); |
|
136 run_next_test(); |
|
137 }); |
|
138 }); |
|
139 |
|
140 add_test(function test_nullCallback() { |
|
141 let counts = get_telemetry_counts(); |
|
142 try { |
|
143 gAppRep.queryReputation({ |
|
144 sourceURI: createURI("http://example.com"), |
|
145 fileSize: 12, |
|
146 }, null); |
|
147 do_throw("Callback cannot be null"); |
|
148 } catch (ex if ex.result == Cr.NS_ERROR_INVALID_POINTER) { |
|
149 // We don't even increment the count here, because there's no callback. |
|
150 check_telemetry(counts.total, counts.shouldBlock, counts.listCounts); |
|
151 run_next_test(); |
|
152 } |
|
153 }); |
|
154 |
|
155 add_test(function test_disabled() { |
|
156 let counts = get_telemetry_counts(); |
|
157 Services.prefs.setCharPref("browser.safebrowsing.appRepURL", ""); |
|
158 gAppRep.queryReputation({ |
|
159 sourceURI: createURI("http://example.com"), |
|
160 fileSize: 12, |
|
161 }, function onComplete(aShouldBlock, aStatus) { |
|
162 // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled |
|
163 do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus); |
|
164 do_check_false(aShouldBlock); |
|
165 check_telemetry(counts.total + 1, counts.shouldBlock, counts.listCounts); |
|
166 run_next_test(); |
|
167 }); |
|
168 }); |
|
169 |
|
170 // Set up the local whitelist. |
|
171 add_test(function test_local_list() { |
|
172 // Construct a response with redirect urls. |
|
173 function processUpdateRequest() { |
|
174 let response = "n:1000\n"; |
|
175 for (let table in gTables) { |
|
176 response += "i:" + table + "\n"; |
|
177 for (let i = 0; i < gTables[table].length; ++i) { |
|
178 response += "u:" + gTables[table][i] + "\n"; |
|
179 } |
|
180 } |
|
181 do_print("Returning update response: " + response); |
|
182 return response; |
|
183 } |
|
184 gHttpServ.registerPathHandler("/downloads", function(request, response) { |
|
185 let buf = NetUtil.readInputStreamToString(request.bodyInputStream, |
|
186 request.bodyInputStream.available()); |
|
187 let blob = processUpdateRequest(); |
|
188 response.setHeader("Content-Type", |
|
189 "application/vnd.google.safebrowsing-update", false); |
|
190 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
191 response.bodyOutputStream.write(blob, blob.length); |
|
192 }); |
|
193 |
|
194 let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"] |
|
195 .getService(Ci.nsIUrlClassifierStreamUpdater); |
|
196 streamUpdater.updateUrl = "http://localhost:4444/downloads"; |
|
197 |
|
198 // Load up some update chunks for the safebrowsing server to serve. |
|
199 // This chunk contains the hash of whitelisted.com/. |
|
200 registerTableUpdate("goog-badbinurl-shavar", "data/block_digest.chunk"); |
|
201 // This chunk contains the hash of blocklisted.com/. |
|
202 registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk"); |
|
203 |
|
204 // Download some updates, and don't continue until the downloads are done. |
|
205 function updateSuccess(aEvent) { |
|
206 // Timeout of n:1000 is constructed in processUpdateRequest above and |
|
207 // passed back in the callback in nsIUrlClassifierStreamUpdater on success. |
|
208 do_check_eq("1000", aEvent); |
|
209 do_print("All data processed"); |
|
210 run_next_test(); |
|
211 } |
|
212 // Just throw if we ever get an update or download error. |
|
213 function handleError(aEvent) { |
|
214 do_throw("We didn't download or update correctly: " + aEvent); |
|
215 } |
|
216 streamUpdater.downloadUpdates( |
|
217 "goog-downloadwhite-digest256,goog-badbinurl-shavar", |
|
218 "goog-downloadwhite-digest256,goog-badbinurl-shavar;\n", |
|
219 updateSuccess, handleError, handleError); |
|
220 }); |
|
221 |
|
222 add_test(function test_unlisted() { |
|
223 Services.prefs.setCharPref("browser.safebrowsing.appRepURL", |
|
224 "http://localhost:4444/download"); |
|
225 let counts = get_telemetry_counts(); |
|
226 let listCounts = counts.listCounts; |
|
227 listCounts[NO_LIST]++; |
|
228 gAppRep.queryReputation({ |
|
229 sourceURI: createURI("http://example.com"), |
|
230 fileSize: 12, |
|
231 }, function onComplete(aShouldBlock, aStatus) { |
|
232 do_check_eq(Cr.NS_OK, aStatus); |
|
233 do_check_false(aShouldBlock); |
|
234 check_telemetry(counts.total + 1, counts.shouldBlock, listCounts); |
|
235 run_next_test(); |
|
236 }); |
|
237 }); |
|
238 |
|
239 add_test(function test_local_blacklist() { |
|
240 Services.prefs.setCharPref("browser.safebrowsing.appRepURL", |
|
241 "http://localhost:4444/download"); |
|
242 let counts = get_telemetry_counts(); |
|
243 let listCounts = counts.listCounts; |
|
244 listCounts[BLOCK_LIST]++; |
|
245 gAppRep.queryReputation({ |
|
246 sourceURI: createURI("http://blocklisted.com"), |
|
247 fileSize: 12, |
|
248 }, function onComplete(aShouldBlock, aStatus) { |
|
249 do_check_eq(Cr.NS_OK, aStatus); |
|
250 do_check_true(aShouldBlock); |
|
251 check_telemetry(counts.total + 1, counts.shouldBlock + 1, listCounts); |
|
252 run_next_test(); |
|
253 }); |
|
254 }); |
|
255 |
|
256 add_test(function test_referer_blacklist() { |
|
257 Services.prefs.setCharPref("browser.safebrowsing.appRepURL", |
|
258 "http://localhost:4444/download"); |
|
259 let counts = get_telemetry_counts(); |
|
260 let listCounts = counts.listCounts; |
|
261 listCounts[BLOCK_LIST]++; |
|
262 gAppRep.queryReputation({ |
|
263 sourceURI: createURI("http://example.com"), |
|
264 referrerURI: createURI("http://blocklisted.com"), |
|
265 fileSize: 12, |
|
266 }, function onComplete(aShouldBlock, aStatus) { |
|
267 do_check_eq(Cr.NS_OK, aStatus); |
|
268 do_check_true(aShouldBlock); |
|
269 check_telemetry(counts.total + 1, counts.shouldBlock + 1, listCounts); |
|
270 run_next_test(); |
|
271 }); |
|
272 }); |
|
273 |
|
274 add_test(function test_blocklist_trumps_allowlist() { |
|
275 Services.prefs.setCharPref("browser.safebrowsing.appRepURL", |
|
276 "http://localhost:4444/download"); |
|
277 let counts = get_telemetry_counts(); |
|
278 let listCounts = counts.listCounts; |
|
279 listCounts[BLOCK_LIST]++; |
|
280 gAppRep.queryReputation({ |
|
281 sourceURI: createURI("http://whitelisted.com"), |
|
282 referrerURI: createURI("http://blocklisted.com"), |
|
283 fileSize: 12, |
|
284 }, function onComplete(aShouldBlock, aStatus) { |
|
285 do_check_eq(Cr.NS_OK, aStatus); |
|
286 do_check_true(aShouldBlock); |
|
287 check_telemetry(counts.total + 1, counts.shouldBlock + 1, listCounts); |
|
288 run_next_test(); |
|
289 }); |
|
290 }); |