/src/mozilla-central/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | #include "chromium/safebrowsing.pb.h" |
6 | | #include "nsEscape.h" |
7 | | #include "nsString.h" |
8 | | #include "nsIURI.h" |
9 | | #include "nsIURIMutator.h" |
10 | | #include "nsIURL.h" |
11 | | #include "nsUrlClassifierUtils.h" |
12 | | #include "nsTArray.h" |
13 | | #include "nsReadableUtils.h" |
14 | | #include "plbase64.h" |
15 | | #include "nsPrintfCString.h" |
16 | | #include "mozilla/Sprintf.h" |
17 | | #include "mozilla/Mutex.h" |
18 | | #include "nsIRedirectHistoryEntry.h" |
19 | | #include "nsIHttpChannelInternal.h" |
20 | | #include "mozIThirdPartyUtil.h" |
21 | | #include "nsIDocShell.h" |
22 | | #include "mozilla/TextUtils.h" |
23 | | |
24 | 0 | #define DEFAULT_PROTOCOL_VERSION "2.2" |
25 | | |
26 | | static char int_to_hex_digit(int32_t i) |
27 | 0 | { |
28 | 0 | NS_ASSERTION((i >= 0) && (i <= 15), "int too big in int_to_hex_digit"); |
29 | 0 | return static_cast<char>(((i < 10) ? (i + '0') : ((i - 10) + 'A'))); |
30 | 0 | } |
31 | | |
32 | | static bool |
33 | | IsDecimal(const nsACString & num) |
34 | 0 | { |
35 | 0 | for (uint32_t i = 0; i < num.Length(); i++) { |
36 | 0 | if (!mozilla::IsAsciiDigit(num[i])) { |
37 | 0 | return false; |
38 | 0 | } |
39 | 0 | } |
40 | 0 |
|
41 | 0 | return true; |
42 | 0 | } |
43 | | |
44 | | static bool |
45 | | IsHex(const nsACString & num) |
46 | 0 | { |
47 | 0 | if (num.Length() < 3) { |
48 | 0 | return false; |
49 | 0 | } |
50 | 0 | |
51 | 0 | if (num[0] != '0' || !(num[1] == 'x' || num[1] == 'X')) { |
52 | 0 | return false; |
53 | 0 | } |
54 | 0 | |
55 | 0 | for (uint32_t i = 2; i < num.Length(); i++) { |
56 | 0 | if (!mozilla::IsAsciiHexDigit(num[i])) { |
57 | 0 | return false; |
58 | 0 | } |
59 | 0 | } |
60 | 0 |
|
61 | 0 | return true; |
62 | 0 | } |
63 | | |
64 | | static bool |
65 | | IsOctal(const nsACString & num) |
66 | 0 | { |
67 | 0 | if (num.Length() < 2) { |
68 | 0 | return false; |
69 | 0 | } |
70 | 0 | |
71 | 0 | if (num[0] != '0') { |
72 | 0 | return false; |
73 | 0 | } |
74 | 0 | |
75 | 0 | for (uint32_t i = 1; i < num.Length(); i++) { |
76 | 0 | if (!mozilla::IsAsciiDigit(num[i]) || num[i] == '8' || num[i] == '9') { |
77 | 0 | return false; |
78 | 0 | } |
79 | 0 | } |
80 | 0 |
|
81 | 0 | return true; |
82 | 0 | } |
83 | | |
84 | | ///////////////////////////////////////////////////////////////// |
85 | | // SafeBrowsing V4 related utits. |
86 | | |
87 | | namespace mozilla { |
88 | | namespace safebrowsing { |
89 | | |
90 | | static PlatformType |
91 | | GetPlatformType() |
92 | 0 | { |
93 | | #if defined(ANDROID) |
94 | | return ANDROID_PLATFORM; |
95 | | #elif defined(XP_MACOSX) |
96 | | return OSX_PLATFORM; |
97 | | #elif defined(XP_LINUX) |
98 | | return LINUX_PLATFORM; |
99 | | #elif defined(XP_WIN) |
100 | | return WINDOWS_PLATFORM; |
101 | | #else |
102 | | // Default to Linux for other platforms (see bug 1362501). |
103 | | return LINUX_PLATFORM; |
104 | | #endif |
105 | | } |
106 | | |
107 | | typedef FetchThreatListUpdatesRequest_ListUpdateRequest ListUpdateRequest; |
108 | | typedef FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints Constraints; |
109 | | |
110 | | static void |
111 | | InitListUpdateRequest(ThreatType aThreatType, |
112 | | const char* aStateBase64, |
113 | | ListUpdateRequest* aListUpdateRequest) |
114 | 0 | { |
115 | 0 | aListUpdateRequest->set_threat_type(aThreatType); |
116 | 0 | PlatformType platform = GetPlatformType(); |
117 | | #if defined(ANDROID) |
118 | | // Temporary hack to fix bug 1441345. |
119 | | if ((aThreatType == SOCIAL_ENGINEERING_PUBLIC) || |
120 | | (aThreatType == SOCIAL_ENGINEERING)) { |
121 | | platform = LINUX_PLATFORM; |
122 | | } |
123 | | #endif |
124 | | aListUpdateRequest->set_platform_type(platform); |
125 | 0 | aListUpdateRequest->set_threat_entry_type(URL); |
126 | 0 |
|
127 | 0 | Constraints* contraints = new Constraints(); |
128 | 0 | contraints->add_supported_compressions(RICE); |
129 | 0 | aListUpdateRequest->set_allocated_constraints(contraints); |
130 | 0 |
|
131 | 0 | // Only set non-empty state. |
132 | 0 | if (aStateBase64[0] != '\0') { |
133 | 0 | nsCString stateBinary; |
134 | 0 | nsresult rv = Base64Decode(nsDependentCString(aStateBase64), stateBinary); |
135 | 0 | if (NS_SUCCEEDED(rv)) { |
136 | 0 | aListUpdateRequest->set_state(stateBinary.get(), stateBinary.Length()); |
137 | 0 | } |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | static ClientInfo* |
142 | | CreateClientInfo() |
143 | 0 | { |
144 | 0 | ClientInfo* c = new ClientInfo(); |
145 | 0 |
|
146 | 0 | nsCOMPtr<nsIPrefBranch> prefBranch = |
147 | 0 | do_GetService(NS_PREFSERVICE_CONTRACTID); |
148 | 0 |
|
149 | 0 | nsAutoCString clientId; |
150 | 0 | nsresult rv = prefBranch->GetCharPref("browser.safebrowsing.id", clientId); |
151 | 0 |
|
152 | 0 | if (NS_FAILED(rv)) { |
153 | 0 | clientId = "Firefox"; // Use "Firefox" as fallback. |
154 | 0 | } |
155 | 0 |
|
156 | 0 | c->set_client_id(clientId.get()); |
157 | 0 |
|
158 | 0 | return c; |
159 | 0 | } |
160 | | |
161 | | static bool |
162 | | IsAllowedOnCurrentPlatform(uint32_t aThreatType) |
163 | 0 | { |
164 | 0 | PlatformType platform = GetPlatformType(); |
165 | 0 |
|
166 | 0 | switch (aThreatType) { |
167 | 0 | case POTENTIALLY_HARMFUL_APPLICATION: |
168 | 0 | // Bug 1388582 - Google server would respond 404 error if the request |
169 | 0 | // contains PHA on non-mobile platform. |
170 | 0 | return ANDROID_PLATFORM == platform; |
171 | 0 | case MALICIOUS_BINARY: |
172 | 0 | case CSD_DOWNLOAD_WHITELIST: |
173 | 0 | // Bug 1392204 - 'goog-downloadwhite-proto' and 'goog-badbinurl-proto' |
174 | 0 | // are not available on android. |
175 | 0 | return ANDROID_PLATFORM != platform; |
176 | 0 | } |
177 | 0 | // We allow every threat type not listed in the switch cases. |
178 | 0 | return true; |
179 | 0 | } |
180 | | |
181 | | } // end of namespace safebrowsing. |
182 | | } // end of namespace mozilla. |
183 | | |
184 | | nsUrlClassifierUtils::nsUrlClassifierUtils() |
185 | | : mProviderDictLock("nsUrlClassifierUtils.mProviderDictLock") |
186 | 0 | { |
187 | 0 | } |
188 | | |
189 | | nsresult |
190 | | nsUrlClassifierUtils::Init() |
191 | 0 | { |
192 | 0 | // nsIUrlClassifierUtils is a thread-safe service so it's |
193 | 0 | // allowed to use on non-main threads. However, building |
194 | 0 | // the provider dictionary must be on the main thread. |
195 | 0 | // We forcefully load nsUrlClassifierUtils in |
196 | 0 | // nsUrlClassifierDBService::Init() to ensure we must |
197 | 0 | // now be on the main thread. |
198 | 0 | nsresult rv = ReadProvidersFromPrefs(mProviderDict); |
199 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
200 | 0 |
|
201 | 0 | // Add an observer for shutdown |
202 | 0 | nsCOMPtr<nsIObserverService> observerService = |
203 | 0 | mozilla::services::GetObserverService(); |
204 | 0 | if (!observerService) |
205 | 0 | return NS_ERROR_FAILURE; |
206 | 0 | |
207 | 0 | observerService->AddObserver(this, "xpcom-shutdown-threads", false); |
208 | 0 | Preferences::AddStrongObserver(this, "browser.safebrowsing"); |
209 | 0 |
|
210 | 0 | return NS_OK; |
211 | 0 | } |
212 | | |
213 | | NS_IMPL_ISUPPORTS(nsUrlClassifierUtils, |
214 | | nsIUrlClassifierUtils, |
215 | | nsIObserver) |
216 | | |
217 | | ///////////////////////////////////////////////////////////////////////////// |
218 | | // nsIUrlClassifierUtils |
219 | | |
220 | | NS_IMETHODIMP |
221 | | nsUrlClassifierUtils::GetKeyForURI(nsIURI * uri, nsACString & _retval) |
222 | 0 | { |
223 | 0 | nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri); |
224 | 0 | if (!innerURI) |
225 | 0 | innerURI = uri; |
226 | 0 |
|
227 | 0 | nsAutoCString host; |
228 | 0 | innerURI->GetAsciiHost(host); |
229 | 0 |
|
230 | 0 | if (host.IsEmpty()) { |
231 | 0 | return NS_ERROR_MALFORMED_URI; |
232 | 0 | } |
233 | 0 | |
234 | 0 | nsresult rv = CanonicalizeHostname(host, _retval); |
235 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
236 | 0 |
|
237 | 0 | nsAutoCString path; |
238 | 0 | rv = innerURI->GetPathQueryRef(path); |
239 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
240 | 0 |
|
241 | 0 | // strip out anchors |
242 | 0 | int32_t ref = path.FindChar('#'); |
243 | 0 | if (ref != kNotFound) |
244 | 0 | path.SetLength(ref); |
245 | 0 |
|
246 | 0 | nsAutoCString temp; |
247 | 0 | rv = CanonicalizePath(path, temp); |
248 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
249 | 0 |
|
250 | 0 | _retval.Append(temp); |
251 | 0 |
|
252 | 0 | return NS_OK; |
253 | 0 | } |
254 | | |
255 | | // We use "goog-*-proto" as the list name for v4, where "proto" indicates |
256 | | // it's updated (as well as hash completion) via protobuf. |
257 | | // |
258 | | // In the mozilla official build, we are allowed to use the |
259 | | // private phishing list (goog-phish-proto). See Bug 1288840. |
260 | | static const struct { |
261 | | const char* mListName; |
262 | | uint32_t mThreatType; |
263 | | } THREAT_TYPE_CONV_TABLE[] = { |
264 | | { "goog-malware-proto", MALWARE_THREAT}, // 1 |
265 | | { "googpub-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2 |
266 | | { "goog-unwanted-proto", UNWANTED_SOFTWARE}, // 3 |
267 | | { "goog-harmful-proto", POTENTIALLY_HARMFUL_APPLICATION}, // 4 |
268 | | { "goog-phish-proto", SOCIAL_ENGINEERING}, // 5 |
269 | | |
270 | | // For application reputation |
271 | | { "goog-badbinurl-proto", MALICIOUS_BINARY}, // 7 |
272 | | { "goog-downloadwhite-proto", CSD_DOWNLOAD_WHITELIST}, // 9 |
273 | | |
274 | | // For login reputation |
275 | | { "goog-passwordwhite-proto", CSD_WHITELIST}, // 8 |
276 | | |
277 | | // For testing purpose. |
278 | | { "test-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2 |
279 | | { "test-unwanted-proto", UNWANTED_SOFTWARE}, // 3 |
280 | | { "test-passwordwhite-proto", CSD_WHITELIST}, // 8 |
281 | | }; |
282 | | |
283 | | NS_IMETHODIMP |
284 | | nsUrlClassifierUtils::ConvertThreatTypeToListNames(uint32_t aThreatType, |
285 | | nsACString& aListNames) |
286 | 0 | { |
287 | 0 | for (uint32_t i = 0; i < ArrayLength(THREAT_TYPE_CONV_TABLE); i++) { |
288 | 0 | if (aThreatType == THREAT_TYPE_CONV_TABLE[i].mThreatType) { |
289 | 0 | if (!aListNames.IsEmpty()) { |
290 | 0 | aListNames.AppendLiteral(","); |
291 | 0 | } |
292 | 0 | aListNames += THREAT_TYPE_CONV_TABLE[i].mListName; |
293 | 0 | } |
294 | 0 | } |
295 | 0 |
|
296 | 0 | return aListNames.IsEmpty() ? NS_ERROR_FAILURE : NS_OK; |
297 | 0 | } |
298 | | |
299 | | NS_IMETHODIMP |
300 | | nsUrlClassifierUtils::ConvertListNameToThreatType(const nsACString& aListName, |
301 | | uint32_t* aThreatType) |
302 | 0 | { |
303 | 0 | for (uint32_t i = 0; i < ArrayLength(THREAT_TYPE_CONV_TABLE); i++) { |
304 | 0 | if (aListName.EqualsASCII(THREAT_TYPE_CONV_TABLE[i].mListName)) { |
305 | 0 | *aThreatType = THREAT_TYPE_CONV_TABLE[i].mThreatType; |
306 | 0 | return NS_OK; |
307 | 0 | } |
308 | 0 | } |
309 | 0 |
|
310 | 0 | return NS_ERROR_FAILURE; |
311 | 0 | } |
312 | | |
313 | | NS_IMETHODIMP |
314 | | nsUrlClassifierUtils::GetProvider(const nsACString& aTableName, |
315 | | nsACString& aProvider) |
316 | 0 | { |
317 | 0 | MutexAutoLock lock(mProviderDictLock); |
318 | 0 | nsCString* provider = nullptr; |
319 | 0 | if (StringBeginsWith(aTableName, NS_LITERAL_CSTRING("test"))) { |
320 | 0 | aProvider = NS_LITERAL_CSTRING(TESTING_TABLE_PROVIDER_NAME); |
321 | 0 | } else if (mProviderDict.Get(aTableName, &provider)) { |
322 | 0 | aProvider = provider ? *provider : EmptyCString(); |
323 | 0 | } else { |
324 | 0 | aProvider = EmptyCString(); |
325 | 0 | } |
326 | 0 | return NS_OK; |
327 | 0 | } |
328 | | |
329 | | NS_IMETHODIMP |
330 | | nsUrlClassifierUtils::GetTelemetryProvider(const nsACString& aTableName, |
331 | | nsACString& aProvider) |
332 | 0 | { |
333 | 0 | GetProvider(aTableName, aProvider); |
334 | 0 | // Whitelist known providers to avoid reporting on private ones. |
335 | 0 | // An empty provider is treated as "other" |
336 | 0 | if (!NS_LITERAL_CSTRING("mozilla").Equals(aProvider) && |
337 | 0 | !NS_LITERAL_CSTRING("google").Equals(aProvider) && |
338 | 0 | !NS_LITERAL_CSTRING("google4").Equals(aProvider) && |
339 | 0 | !NS_LITERAL_CSTRING("baidu").Equals(aProvider) && |
340 | 0 | !NS_LITERAL_CSTRING("mozcn").Equals(aProvider) && |
341 | 0 | !NS_LITERAL_CSTRING("yandex").Equals(aProvider) && |
342 | 0 | !NS_LITERAL_CSTRING(TESTING_TABLE_PROVIDER_NAME).Equals(aProvider)) { |
343 | 0 | aProvider.AssignLiteral("other"); |
344 | 0 | } |
345 | 0 |
|
346 | 0 | return NS_OK; |
347 | 0 | } |
348 | | |
349 | | NS_IMETHODIMP |
350 | | nsUrlClassifierUtils::GetProtocolVersion(const nsACString& aProvider, |
351 | | nsACString& aVersion) |
352 | 0 | { |
353 | 0 | nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); |
354 | 0 | if (prefBranch) { |
355 | 0 | nsPrintfCString prefName("browser.safebrowsing.provider.%s.pver", |
356 | 0 | nsCString(aProvider).get()); |
357 | 0 | nsAutoCString version; |
358 | 0 | nsresult rv = prefBranch->GetCharPref(prefName.get(), version); |
359 | 0 |
|
360 | 0 | aVersion = NS_SUCCEEDED(rv) ? version.get() : DEFAULT_PROTOCOL_VERSION; |
361 | 0 | } else { |
362 | 0 | aVersion = DEFAULT_PROTOCOL_VERSION; |
363 | 0 | } |
364 | 0 |
|
365 | 0 | return NS_OK; |
366 | 0 | } |
367 | | |
368 | | NS_IMETHODIMP |
369 | | nsUrlClassifierUtils::MakeUpdateRequestV4(const char** aListNames, |
370 | | const char** aStatesBase64, |
371 | | uint32_t aCount, |
372 | | nsACString &aRequest) |
373 | 0 | { |
374 | 0 | using namespace mozilla::safebrowsing; |
375 | 0 |
|
376 | 0 | FetchThreatListUpdatesRequest r; |
377 | 0 | r.set_allocated_client(CreateClientInfo()); |
378 | 0 |
|
379 | 0 | for (uint32_t i = 0; i < aCount; i++) { |
380 | 0 | nsCString listName(aListNames[i]); |
381 | 0 | uint32_t threatType; |
382 | 0 | nsresult rv = ConvertListNameToThreatType(listName, &threatType); |
383 | 0 | if (NS_FAILED(rv)) { |
384 | 0 | continue; // Unknown list name. |
385 | 0 | } |
386 | 0 | if (!IsAllowedOnCurrentPlatform(threatType)) { |
387 | 0 | NS_WARNING(nsPrintfCString("Threat type %d (%s) is unsupported on current platform: %d", |
388 | 0 | threatType, |
389 | 0 | aListNames[i], |
390 | 0 | GetPlatformType()).get()); |
391 | 0 | continue; // Some threat types are not available on some platforms. |
392 | 0 | } |
393 | 0 | auto lur = r.mutable_list_update_requests()->Add(); |
394 | 0 | InitListUpdateRequest(static_cast<ThreatType>(threatType), aStatesBase64[i], lur); |
395 | 0 | } |
396 | 0 |
|
397 | 0 | // Then serialize. |
398 | 0 | std::string s; |
399 | 0 | r.SerializeToString(&s); |
400 | 0 |
|
401 | 0 | nsCString out; |
402 | 0 | nsresult rv = Base64URLEncode(s.size(), |
403 | 0 | (const uint8_t*)s.c_str(), |
404 | 0 | Base64URLEncodePaddingPolicy::Include, |
405 | 0 | out); |
406 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
407 | 0 |
|
408 | 0 | aRequest = out; |
409 | 0 |
|
410 | 0 | return NS_OK; |
411 | 0 | } |
412 | | |
413 | | NS_IMETHODIMP |
414 | | nsUrlClassifierUtils::MakeFindFullHashRequestV4(const char** aListNames, |
415 | | const char** aListStatesBase64, |
416 | | const char** aPrefixesBase64, |
417 | | uint32_t aListCount, |
418 | | uint32_t aPrefixCount, |
419 | | nsACString &aRequest) |
420 | 0 | { |
421 | 0 | FindFullHashesRequest r; |
422 | 0 | r.set_allocated_client(CreateClientInfo()); |
423 | 0 |
|
424 | 0 | nsresult rv; |
425 | 0 |
|
426 | 0 | //------------------------------------------------------------------- |
427 | 0 | // Set up FindFullHashesRequest.threat_info. |
428 | 0 | auto threatInfo = r.mutable_threat_info(); |
429 | 0 |
|
430 | 0 | PlatformType platform = GetPlatformType(); |
431 | 0 |
|
432 | 0 | // 1) Set threat types. |
433 | 0 | for (uint32_t i = 0; i < aListCount; i++) { |
434 | 0 | // Add threat types. |
435 | 0 | uint32_t threatType; |
436 | 0 | rv = ConvertListNameToThreatType(nsDependentCString(aListNames[i]), &threatType); |
437 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
438 | 0 | if (!IsAllowedOnCurrentPlatform(threatType)) { |
439 | 0 | NS_WARNING(nsPrintfCString("Threat type %d (%s) is unsupported on current platform: %d", |
440 | 0 | threatType, |
441 | 0 | aListNames[i], |
442 | 0 | GetPlatformType()).get()); |
443 | 0 | continue; |
444 | 0 | } |
445 | 0 | threatInfo->add_threat_types((ThreatType)threatType); |
446 | 0 |
|
447 | | #if defined(ANDROID) |
448 | | // Temporary hack to fix bug 1441345. |
449 | | if (((ThreatType)threatType == SOCIAL_ENGINEERING_PUBLIC) || |
450 | | ((ThreatType)threatType == SOCIAL_ENGINEERING)) { |
451 | | platform = LINUX_PLATFORM; |
452 | | } |
453 | | #endif |
454 | |
|
455 | 0 | // Add client states for index 'i' only when the threat type is available |
456 | 0 | // on current platform. |
457 | 0 | nsCString stateBinary; |
458 | 0 | rv = Base64Decode(nsDependentCString(aListStatesBase64[i]), stateBinary); |
459 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
460 | 0 | r.add_client_states(stateBinary.get(), stateBinary.Length()); |
461 | 0 | } |
462 | 0 |
|
463 | 0 | // 2) Set platform type. |
464 | 0 | threatInfo->add_platform_types(platform); |
465 | 0 |
|
466 | 0 | // 3) Set threat entry type. |
467 | 0 | threatInfo->add_threat_entry_types(URL); |
468 | 0 |
|
469 | 0 | // 4) Set threat entries. |
470 | 0 | for (uint32_t i = 0; i < aPrefixCount; i++) { |
471 | 0 | nsCString prefixBinary; |
472 | 0 | rv = Base64Decode(nsDependentCString(aPrefixesBase64[i]), prefixBinary); |
473 | 0 | threatInfo->add_threat_entries()->set_hash(prefixBinary.get(), |
474 | 0 | prefixBinary.Length()); |
475 | 0 | } |
476 | 0 | //------------------------------------------------------------------- |
477 | 0 |
|
478 | 0 | // Then serialize. |
479 | 0 | std::string s; |
480 | 0 | r.SerializeToString(&s); |
481 | 0 |
|
482 | 0 | nsCString out; |
483 | 0 | rv = Base64URLEncode(s.size(), |
484 | 0 | (const uint8_t*)s.c_str(), |
485 | 0 | Base64URLEncodePaddingPolicy::Include, |
486 | 0 | out); |
487 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
488 | 0 |
|
489 | 0 | aRequest = out; |
490 | 0 |
|
491 | 0 | return NS_OK; |
492 | 0 | } |
493 | | |
494 | | // Remove ref, query, userpass, anypart which may contain sensitive data |
495 | | static nsresult |
496 | | GetSpecWithoutSensitiveData(nsIURI* aUri, nsACString &aSpec) |
497 | 0 | { |
498 | 0 | if (NS_WARN_IF(!aUri)) { |
499 | 0 | return NS_ERROR_INVALID_ARG; |
500 | 0 | } |
501 | 0 | |
502 | 0 | nsresult rv; |
503 | 0 | nsCOMPtr<nsIURL> url(do_QueryInterface(aUri)); |
504 | 0 | if (url) { |
505 | 0 | nsCOMPtr<nsIURI> clone; |
506 | 0 | rv = NS_MutateURI(url) |
507 | 0 | .SetQuery(EmptyCString()) |
508 | 0 | .SetRef(EmptyCString()) |
509 | 0 | .SetUserPass(EmptyCString()) |
510 | 0 | .Finalize(clone); |
511 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
512 | 0 | rv = clone->GetAsciiSpec(aSpec); |
513 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
514 | 0 | } |
515 | 0 | return NS_OK; |
516 | 0 | } |
517 | | |
518 | | static nsresult |
519 | | AddThreatSourceFromChannel(ThreatHit& aHit, nsIChannel *aChannel, |
520 | | ThreatHit_ThreatSourceType aType) |
521 | 0 | { |
522 | 0 | if (NS_WARN_IF(!aChannel)) { |
523 | 0 | return NS_ERROR_INVALID_ARG; |
524 | 0 | } |
525 | 0 | |
526 | 0 | nsresult rv; |
527 | 0 |
|
528 | 0 | auto matchingSource = aHit.add_resources(); |
529 | 0 | matchingSource->set_type(aType); |
530 | 0 |
|
531 | 0 | nsCOMPtr<nsIURI> uri; |
532 | 0 | rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); |
533 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
534 | 0 |
|
535 | 0 | nsCString spec; |
536 | 0 | rv = GetSpecWithoutSensitiveData(uri, spec); |
537 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
538 | 0 | matchingSource->set_url(spec.get()); |
539 | 0 |
|
540 | 0 | nsCOMPtr<nsIHttpChannel> httpChannel = |
541 | 0 | do_QueryInterface(aChannel); |
542 | 0 | if (httpChannel) { |
543 | 0 | nsCOMPtr<nsIURI> referrer; |
544 | 0 | rv = httpChannel->GetReferrer(getter_AddRefs(referrer)); |
545 | 0 | if (NS_SUCCEEDED(rv) && referrer) { |
546 | 0 | nsCString referrerSpec; |
547 | 0 | rv = GetSpecWithoutSensitiveData(referrer, referrerSpec); |
548 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
549 | 0 | matchingSource->set_referrer(referrerSpec.get()); |
550 | 0 | } |
551 | 0 | } |
552 | 0 |
|
553 | 0 | nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = |
554 | 0 | do_QueryInterface(aChannel); |
555 | 0 | if (httpChannelInternal) { |
556 | 0 | nsCString remoteIp; |
557 | 0 | rv = httpChannelInternal->GetRemoteAddress(remoteIp); |
558 | 0 | if (NS_SUCCEEDED(rv) && !remoteIp.IsEmpty()) { |
559 | 0 | matchingSource->set_remote_ip(remoteIp.get()); |
560 | 0 | } |
561 | 0 | } |
562 | 0 | return NS_OK; |
563 | 0 | } |
564 | | static nsresult |
565 | | AddThreatSourceFromRedirectEntry(ThreatHit& aHit, |
566 | | nsIRedirectHistoryEntry *aRedirectEntry, |
567 | | ThreatHit_ThreatSourceType aType) |
568 | 0 | { |
569 | 0 | if (NS_WARN_IF(!aRedirectEntry)) { |
570 | 0 | return NS_ERROR_INVALID_ARG; |
571 | 0 | } |
572 | 0 | |
573 | 0 | nsresult rv; |
574 | 0 |
|
575 | 0 | nsCOMPtr<nsIPrincipal> principal; |
576 | 0 | rv = aRedirectEntry->GetPrincipal(getter_AddRefs(principal)); |
577 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
578 | 0 |
|
579 | 0 | nsCOMPtr<nsIURI> uri; |
580 | 0 | rv = principal->GetURI(getter_AddRefs(uri)); |
581 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
582 | 0 |
|
583 | 0 | nsCString spec; |
584 | 0 | rv = GetSpecWithoutSensitiveData(uri, spec); |
585 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
586 | 0 | auto source = aHit.add_resources(); |
587 | 0 | source->set_url(spec.get()); |
588 | 0 | source->set_type(aType); |
589 | 0 |
|
590 | 0 | nsCOMPtr<nsIURI> referrer; |
591 | 0 | rv = aRedirectEntry->GetReferrerURI(getter_AddRefs(referrer)); |
592 | 0 | if (NS_SUCCEEDED(rv) && referrer) { |
593 | 0 | nsCString referrerSpec; |
594 | 0 | rv = GetSpecWithoutSensitiveData(referrer, referrerSpec); |
595 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
596 | 0 | source->set_referrer(referrerSpec.get()); |
597 | 0 | } |
598 | 0 |
|
599 | 0 | nsCString remoteIp; |
600 | 0 | rv = aRedirectEntry->GetRemoteAddress(remoteIp); |
601 | 0 | if (NS_SUCCEEDED(rv) && !remoteIp.IsEmpty()) { |
602 | 0 | source->set_remote_ip(remoteIp.get()); |
603 | 0 | } |
604 | 0 | return NS_OK; |
605 | 0 | } |
606 | | |
607 | | // Add top level tab url and redirect threatsources to threatHit message |
608 | | static nsresult |
609 | | AddTabThreatSources(ThreatHit& aHit, nsIChannel *aChannel) |
610 | 0 | { |
611 | 0 | if (NS_WARN_IF(!aChannel)) { |
612 | 0 | return NS_ERROR_INVALID_ARG; |
613 | 0 | } |
614 | 0 | |
615 | 0 | nsresult rv; |
616 | 0 | nsCOMPtr<mozIDOMWindowProxy> win; |
617 | 0 | nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = |
618 | 0 | do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); |
619 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
620 | 0 |
|
621 | 0 | rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, getter_AddRefs(win)); |
622 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
623 | 0 |
|
624 | 0 | auto* pwin = nsPIDOMWindowOuter::From(win); |
625 | 0 | nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); |
626 | 0 | if (!docShell) { |
627 | 0 | return NS_OK; |
628 | 0 | } |
629 | 0 | |
630 | 0 | nsCOMPtr<nsIChannel> topChannel; |
631 | 0 | docShell->GetCurrentDocumentChannel(getter_AddRefs(topChannel)); |
632 | 0 | if (!topChannel) { |
633 | 0 | return NS_OK; |
634 | 0 | } |
635 | 0 | |
636 | 0 | nsCOMPtr<nsIURI> uri; |
637 | 0 | rv = aChannel->GetURI(getter_AddRefs(uri)); |
638 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
639 | 0 |
|
640 | 0 | nsCOMPtr<nsIURI> topUri; |
641 | 0 | rv = topChannel->GetURI(getter_AddRefs(topUri)); |
642 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
643 | 0 |
|
644 | 0 | bool isTopUri = false; |
645 | 0 | rv = topUri->Equals(uri, &isTopUri); |
646 | 0 | if (NS_SUCCEEDED(rv) && !isTopUri) { |
647 | 0 | nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); |
648 | 0 | if (loadInfo && loadInfo->RedirectChain().Length()) { |
649 | 0 | AddThreatSourceFromRedirectEntry(aHit, loadInfo->RedirectChain()[0], |
650 | 0 | ThreatHit_ThreatSourceType_TAB_RESOURCE); |
651 | 0 | } |
652 | 0 | } |
653 | 0 |
|
654 | 0 | // Set top level tab_url threat source |
655 | 0 | rv = AddThreatSourceFromChannel(aHit, topChannel, |
656 | 0 | ThreatHit_ThreatSourceType_TAB_URL); |
657 | 0 | Unused << NS_WARN_IF(NS_FAILED(rv)); |
658 | 0 |
|
659 | 0 | // Set tab_redirect threat sources if there's any |
660 | 0 | nsCOMPtr<nsILoadInfo> topLoadInfo = topChannel->GetLoadInfo(); |
661 | 0 | if (!topLoadInfo) { |
662 | 0 | return NS_OK; |
663 | 0 | } |
664 | 0 | |
665 | 0 | nsIRedirectHistoryEntry* redirectEntry; |
666 | 0 | size_t length = topLoadInfo->RedirectChain().Length(); |
667 | 0 | for (size_t i = 0; i < length; i++) { |
668 | 0 | redirectEntry = topLoadInfo->RedirectChain()[i]; |
669 | 0 | AddThreatSourceFromRedirectEntry(aHit, redirectEntry, |
670 | 0 | ThreatHit_ThreatSourceType_TAB_REDIRECT); |
671 | 0 | } |
672 | 0 |
|
673 | 0 | return NS_OK; |
674 | 0 | } |
675 | | |
676 | | NS_IMETHODIMP |
677 | | nsUrlClassifierUtils::MakeThreatHitReport(nsIChannel *aChannel, |
678 | | const nsACString& aListName, |
679 | | const nsACString& aHashBase64, |
680 | | nsACString &aRequest) |
681 | 0 | { |
682 | 0 | if (NS_WARN_IF(aListName.IsEmpty()) || |
683 | 0 | NS_WARN_IF(aHashBase64.IsEmpty()) || |
684 | 0 | NS_WARN_IF(!aChannel)) { |
685 | 0 | return NS_ERROR_INVALID_ARG; |
686 | 0 | } |
687 | 0 | |
688 | 0 | ThreatHit hit; |
689 | 0 | nsresult rv; |
690 | 0 |
|
691 | 0 | uint32_t threatType; |
692 | 0 | rv = ConvertListNameToThreatType(aListName, &threatType); |
693 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
694 | 0 | hit.set_threat_type(static_cast<ThreatType>(threatType)); |
695 | 0 |
|
696 | 0 | hit.set_platform_type(GetPlatformType()); |
697 | 0 |
|
698 | 0 | nsCString hash; |
699 | 0 | rv = Base64Decode(aHashBase64, hash); |
700 | 0 | if (NS_FAILED(rv) || hash.Length() != COMPLETE_SIZE) { |
701 | 0 | return NS_ERROR_FAILURE; |
702 | 0 | } |
703 | 0 | |
704 | 0 | auto threatEntry = hit.mutable_entry(); |
705 | 0 | threatEntry->set_hash(hash.get(), hash.Length()); |
706 | 0 |
|
707 | 0 | // Set matching source |
708 | 0 | rv = AddThreatSourceFromChannel(hit, aChannel, |
709 | 0 | ThreatHit_ThreatSourceType_MATCHING_URL); |
710 | 0 | Unused << NS_WARN_IF(NS_FAILED(rv)); |
711 | 0 | // Set tab url, tab resource url and redirect sources |
712 | 0 | rv = AddTabThreatSources(hit, aChannel); |
713 | 0 | Unused << NS_WARN_IF(NS_FAILED(rv)); |
714 | 0 |
|
715 | 0 | hit.set_allocated_client_info(CreateClientInfo()); |
716 | 0 |
|
717 | 0 | std::string s; |
718 | 0 | hit.SerializeToString(&s); |
719 | 0 |
|
720 | 0 | nsCString out; |
721 | 0 | rv = Base64URLEncode(s.size(), |
722 | 0 | reinterpret_cast<const uint8_t*>(s.c_str()), |
723 | 0 | Base64URLEncodePaddingPolicy::Include, |
724 | 0 | out); |
725 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
726 | 0 |
|
727 | 0 | aRequest = out; |
728 | 0 |
|
729 | 0 | return NS_OK; |
730 | 0 | } |
731 | | |
732 | | static uint32_t |
733 | | DurationToMs(const Duration& aDuration) |
734 | 0 | { |
735 | 0 | // Seconds precision is good enough. Ignore nanoseconds like Chrome does. |
736 | 0 | return aDuration.seconds() * 1000; |
737 | 0 | } |
738 | | |
739 | | NS_IMETHODIMP |
740 | | nsUrlClassifierUtils::ParseFindFullHashResponseV4(const nsACString& aResponse, |
741 | | nsIUrlClassifierParseFindFullHashCallback *aCallback) |
742 | 0 | { |
743 | 0 | enum CompletionErrorType { |
744 | 0 | SUCCESS = 0, |
745 | 0 | PARSING_FAILURE = 1, |
746 | 0 | UNKNOWN_THREAT_TYPE = 2, |
747 | 0 | }; |
748 | 0 |
|
749 | 0 | FindFullHashesResponse r; |
750 | 0 | if (!r.ParseFromArray(aResponse.BeginReading(), aResponse.Length())) { |
751 | 0 | NS_WARNING("Invalid response"); |
752 | 0 | Telemetry::Accumulate(Telemetry::URLCLASSIFIER_COMPLETION_ERROR, |
753 | 0 | PARSING_FAILURE); |
754 | 0 | return NS_ERROR_FAILURE; |
755 | 0 | } |
756 | 0 |
|
757 | 0 | bool hasUnknownThreatType = false; |
758 | 0 |
|
759 | 0 | for (auto& m : r.matches()) { |
760 | 0 | nsCString tableNames; |
761 | 0 | nsresult rv = ConvertThreatTypeToListNames(m.threat_type(), tableNames); |
762 | 0 | if (NS_FAILED(rv)) { |
763 | 0 | hasUnknownThreatType = true; |
764 | 0 | continue; // Ignore un-convertable threat type. |
765 | 0 | } |
766 | 0 | auto& hash = m.threat().hash(); |
767 | 0 | auto cacheDurationSec = m.cache_duration().seconds(); |
768 | 0 | aCallback->OnCompleteHashFound(nsDependentCString(hash.c_str(), hash.length()), |
769 | 0 | tableNames, cacheDurationSec); |
770 | 0 |
|
771 | 0 | Telemetry::Accumulate(Telemetry::URLCLASSIFIER_POSITIVE_CACHE_DURATION, |
772 | 0 | cacheDurationSec * PR_MSEC_PER_SEC); |
773 | 0 | } |
774 | 0 |
|
775 | 0 | auto minWaitDuration = DurationToMs(r.minimum_wait_duration()); |
776 | 0 | auto negCacheDurationSec = r.negative_cache_duration().seconds(); |
777 | 0 |
|
778 | 0 | aCallback->OnResponseParsed(minWaitDuration, negCacheDurationSec); |
779 | 0 |
|
780 | 0 | Telemetry::Accumulate(Telemetry::URLCLASSIFIER_COMPLETION_ERROR, |
781 | 0 | hasUnknownThreatType ? UNKNOWN_THREAT_TYPE : SUCCESS); |
782 | 0 |
|
783 | 0 | Telemetry::Accumulate(Telemetry::URLCLASSIFIER_NEGATIVE_CACHE_DURATION, |
784 | 0 | negCacheDurationSec * PR_MSEC_PER_SEC); |
785 | 0 |
|
786 | 0 | return NS_OK; |
787 | 0 | } |
788 | | |
789 | | ////////////////////////////////////////////////////////// |
790 | | // nsIObserver |
791 | | |
792 | | NS_IMETHODIMP |
793 | | nsUrlClassifierUtils::Observe(nsISupports *aSubject, const char *aTopic, |
794 | | const char16_t *aData) |
795 | 0 | { |
796 | 0 | if (0 == strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { |
797 | 0 | MutexAutoLock lock(mProviderDictLock); |
798 | 0 | return ReadProvidersFromPrefs(mProviderDict); |
799 | 0 | } |
800 | 0 | |
801 | 0 | if (0 == strcmp(aTopic, "xpcom-shutdown-threads")) { |
802 | 0 | nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
803 | 0 | NS_ENSURE_TRUE(prefs, NS_ERROR_FAILURE); |
804 | 0 | return prefs->RemoveObserver("browser.safebrowsing", this); |
805 | 0 | } |
806 | 0 | |
807 | 0 | return NS_ERROR_UNEXPECTED; |
808 | 0 | } |
809 | | |
810 | | ///////////////////////////////////////////////////////////////////////////// |
811 | | // non-interface methods |
812 | | |
813 | | nsresult |
814 | | nsUrlClassifierUtils::ReadProvidersFromPrefs(ProviderDictType& aDict) |
815 | 0 | { |
816 | 0 | MOZ_ASSERT(NS_IsMainThread(), "ReadProvidersFromPrefs must be on main thread"); |
817 | 0 |
|
818 | 0 | nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
819 | 0 | NS_ENSURE_TRUE(prefs, NS_ERROR_FAILURE); |
820 | 0 | nsCOMPtr<nsIPrefBranch> prefBranch; |
821 | 0 | nsresult rv = prefs->GetBranch("browser.safebrowsing.provider.", |
822 | 0 | getter_AddRefs(prefBranch)); |
823 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
824 | 0 |
|
825 | 0 | // We've got a pref branch for "browser.safebrowsing.provider.". |
826 | 0 | // Enumerate all children prefs and parse providers. |
827 | 0 | uint32_t childCount; |
828 | 0 | char** childArray; |
829 | 0 | rv = prefBranch->GetChildList("", &childCount, &childArray); |
830 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
831 | 0 |
|
832 | 0 | // Collect providers from childArray. |
833 | 0 | nsTHashtable<nsCStringHashKey> providers; |
834 | 0 | for (uint32_t i = 0; i < childCount; i++) { |
835 | 0 | nsCString child(childArray[i]); |
836 | 0 | auto dotPos = child.FindChar('.'); |
837 | 0 | if (dotPos < 0) { |
838 | 0 | continue; |
839 | 0 | } |
840 | 0 | |
841 | 0 | nsDependentCSubstring provider = Substring(child, 0, dotPos); |
842 | 0 |
|
843 | 0 | providers.PutEntry(provider); |
844 | 0 | } |
845 | 0 | NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(childCount, childArray); |
846 | 0 |
|
847 | 0 | // Now we have all providers. Check which one owns |aTableName|. |
848 | 0 | // e.g. The owning lists of provider "google" is defined in |
849 | 0 | // "browser.safebrowsing.provider.google.lists". |
850 | 0 | for (auto itr = providers.Iter(); !itr.Done(); itr.Next()) { |
851 | 0 | auto entry = itr.Get(); |
852 | 0 | nsCString provider(entry->GetKey()); |
853 | 0 | nsPrintfCString owninListsPref("%s.lists", provider.get()); |
854 | 0 |
|
855 | 0 | nsAutoCString owningLists; |
856 | 0 | nsresult rv = prefBranch->GetCharPref(owninListsPref.get(), owningLists); |
857 | 0 | if (NS_FAILED(rv)) { |
858 | 0 | continue; |
859 | 0 | } |
860 | 0 | |
861 | 0 | // We've got the owning lists (represented as string) of |provider|. |
862 | 0 | // Build the dictionary for the owning list and the current provider. |
863 | 0 | nsTArray<nsCString> tables; |
864 | 0 | Classifier::SplitTables(owningLists, tables); |
865 | 0 | for (auto tableName : tables) { |
866 | 0 | aDict.Put(tableName, new nsCString(provider)); |
867 | 0 | } |
868 | 0 | } |
869 | 0 |
|
870 | 0 | return NS_OK; |
871 | 0 | } |
872 | | |
873 | | nsresult |
874 | | nsUrlClassifierUtils::CanonicalizeHostname(const nsACString & hostname, |
875 | | nsACString & _retval) |
876 | 0 | { |
877 | 0 | nsAutoCString unescaped; |
878 | 0 | if (!NS_UnescapeURL(PromiseFlatCString(hostname).get(), |
879 | 0 | PromiseFlatCString(hostname).Length(), |
880 | 0 | 0, unescaped)) { |
881 | 0 | unescaped.Assign(hostname); |
882 | 0 | } |
883 | 0 |
|
884 | 0 | nsAutoCString cleaned; |
885 | 0 | CleanupHostname(unescaped, cleaned); |
886 | 0 |
|
887 | 0 | nsAutoCString temp; |
888 | 0 | ParseIPAddress(cleaned, temp); |
889 | 0 | if (!temp.IsEmpty()) { |
890 | 0 | cleaned.Assign(temp); |
891 | 0 | } |
892 | 0 |
|
893 | 0 | ToLowerCase(cleaned); |
894 | 0 | SpecialEncode(cleaned, false, _retval); |
895 | 0 |
|
896 | 0 | return NS_OK; |
897 | 0 | } |
898 | | |
899 | | |
900 | | nsresult |
901 | | nsUrlClassifierUtils::CanonicalizePath(const nsACString & path, |
902 | | nsACString & _retval) |
903 | 0 | { |
904 | 0 | _retval.Truncate(); |
905 | 0 |
|
906 | 0 | nsAutoCString decodedPath(path); |
907 | 0 | nsAutoCString temp; |
908 | 0 | while (NS_UnescapeURL(decodedPath.get(), decodedPath.Length(), 0, temp)) { |
909 | 0 | decodedPath.Assign(temp); |
910 | 0 | temp.Truncate(); |
911 | 0 | } |
912 | 0 |
|
913 | 0 | SpecialEncode(decodedPath, true, _retval); |
914 | 0 | // XXX: lowercase the path? |
915 | 0 |
|
916 | 0 | return NS_OK; |
917 | 0 | } |
918 | | |
919 | | void |
920 | | nsUrlClassifierUtils::CleanupHostname(const nsACString & hostname, |
921 | | nsACString & _retval) |
922 | 0 | { |
923 | 0 | _retval.Truncate(); |
924 | 0 |
|
925 | 0 | const char* curChar = hostname.BeginReading(); |
926 | 0 | const char* end = hostname.EndReading(); |
927 | 0 | char lastChar = '\0'; |
928 | 0 | while (curChar != end) { |
929 | 0 | unsigned char c = static_cast<unsigned char>(*curChar); |
930 | 0 | if (c == '.' && (lastChar == '\0' || lastChar == '.')) { |
931 | 0 | // skip |
932 | 0 | } else { |
933 | 0 | _retval.Append(*curChar); |
934 | 0 | } |
935 | 0 | lastChar = c; |
936 | 0 | ++curChar; |
937 | 0 | } |
938 | 0 |
|
939 | 0 | // cut off trailing dots |
940 | 0 | while (_retval.Length() > 0 && _retval[_retval.Length() - 1] == '.') { |
941 | 0 | _retval.SetLength(_retval.Length() - 1); |
942 | 0 | } |
943 | 0 | } |
944 | | |
945 | | void |
946 | | nsUrlClassifierUtils::ParseIPAddress(const nsACString & host, |
947 | | nsACString & _retval) |
948 | 0 | { |
949 | 0 | _retval.Truncate(); |
950 | 0 | nsACString::const_iterator iter, end; |
951 | 0 | host.BeginReading(iter); |
952 | 0 | host.EndReading(end); |
953 | 0 |
|
954 | 0 | if (host.Length() <= 15) { |
955 | 0 | // The Windows resolver allows a 4-part dotted decimal IP address to |
956 | 0 | // have a space followed by any old rubbish, so long as the total length |
957 | 0 | // of the string doesn't get above 15 characters. So, "10.192.95.89 xy" |
958 | 0 | // is resolved to 10.192.95.89. |
959 | 0 | // If the string length is greater than 15 characters, e.g. |
960 | 0 | // "10.192.95.89 xy.wildcard.example.com", it will be resolved through |
961 | 0 | // DNS. |
962 | 0 |
|
963 | 0 | if (FindCharInReadable(' ', iter, end)) { |
964 | 0 | end = iter; |
965 | 0 | } |
966 | 0 | } |
967 | 0 |
|
968 | 0 | for (host.BeginReading(iter); iter != end; iter++) { |
969 | 0 | if (!(mozilla::IsAsciiHexDigit(*iter) || *iter == 'x' || *iter == 'X' || *iter == '.')) { |
970 | 0 | // not an IP |
971 | 0 | return; |
972 | 0 | } |
973 | 0 | } |
974 | 0 |
|
975 | 0 | host.BeginReading(iter); |
976 | 0 | nsTArray<nsCString> parts; |
977 | 0 | ParseString(PromiseFlatCString(Substring(iter, end)), '.', parts); |
978 | 0 | if (parts.Length() > 4) { |
979 | 0 | return; |
980 | 0 | } |
981 | 0 | |
982 | 0 | // If any potentially-octal numbers (start with 0 but not hex) have |
983 | 0 | // non-octal digits, no part of the ip can be in octal |
984 | 0 | // XXX: this came from the old javascript implementation, is it really |
985 | 0 | // supposed to be like this? |
986 | 0 | bool allowOctal = true; |
987 | 0 | uint32_t i; |
988 | 0 |
|
989 | 0 | for (i = 0; i < parts.Length(); i++) { |
990 | 0 | const nsCString& part = parts[i]; |
991 | 0 | if (part[0] == '0') { |
992 | 0 | for (uint32_t j = 1; j < part.Length(); j++) { |
993 | 0 | if (part[j] == 'x') { |
994 | 0 | break; |
995 | 0 | } |
996 | 0 | if (part[j] == '8' || part[j] == '9') { |
997 | 0 | allowOctal = false; |
998 | 0 | break; |
999 | 0 | } |
1000 | 0 | } |
1001 | 0 | } |
1002 | 0 | } |
1003 | 0 |
|
1004 | 0 | for (i = 0; i < parts.Length(); i++) { |
1005 | 0 | nsAutoCString canonical; |
1006 | 0 |
|
1007 | 0 | if (i == parts.Length() - 1) { |
1008 | 0 | CanonicalNum(parts[i], 5 - parts.Length(), allowOctal, canonical); |
1009 | 0 | } else { |
1010 | 0 | CanonicalNum(parts[i], 1, allowOctal, canonical); |
1011 | 0 | } |
1012 | 0 |
|
1013 | 0 | if (canonical.IsEmpty()) { |
1014 | 0 | _retval.Truncate(); |
1015 | 0 | return; |
1016 | 0 | } |
1017 | 0 | |
1018 | 0 | if (_retval.IsEmpty()) { |
1019 | 0 | _retval.Assign(canonical); |
1020 | 0 | } else { |
1021 | 0 | _retval.Append('.'); |
1022 | 0 | _retval.Append(canonical); |
1023 | 0 | } |
1024 | 0 | } |
1025 | 0 | } |
1026 | | |
1027 | | void |
1028 | | nsUrlClassifierUtils::CanonicalNum(const nsACString& num, |
1029 | | uint32_t bytes, |
1030 | | bool allowOctal, |
1031 | | nsACString& _retval) |
1032 | 0 | { |
1033 | 0 | _retval.Truncate(); |
1034 | 0 |
|
1035 | 0 | if (num.Length() < 1) { |
1036 | 0 | return; |
1037 | 0 | } |
1038 | 0 | |
1039 | 0 | uint32_t val; |
1040 | 0 | if (allowOctal && IsOctal(num)) { |
1041 | 0 | if (PR_sscanf(PromiseFlatCString(num).get(), "%o", &val) != 1) { |
1042 | 0 | return; |
1043 | 0 | } |
1044 | 0 | } else if (IsDecimal(num)) { |
1045 | 0 | if (PR_sscanf(PromiseFlatCString(num).get(), "%u", &val) != 1) { |
1046 | 0 | return; |
1047 | 0 | } |
1048 | 0 | } else if (IsHex(num)) { |
1049 | 0 | if (PR_sscanf(PromiseFlatCString(num).get(), num[1] == 'X' ? "0X%x" : "0x%x", |
1050 | 0 | &val) != 1) { |
1051 | 0 | return; |
1052 | 0 | } |
1053 | 0 | } else { |
1054 | 0 | return; |
1055 | 0 | } |
1056 | 0 | |
1057 | 0 | while (bytes--) { |
1058 | 0 | char buf[20]; |
1059 | 0 | SprintfLiteral(buf, "%u", val & 0xff); |
1060 | 0 | if (_retval.IsEmpty()) { |
1061 | 0 | _retval.Assign(buf); |
1062 | 0 | } else { |
1063 | 0 | _retval = nsDependentCString(buf) + NS_LITERAL_CSTRING(".") + _retval; |
1064 | 0 | } |
1065 | 0 | val >>= 8; |
1066 | 0 | } |
1067 | 0 | } |
1068 | | |
1069 | | // This function will encode all "special" characters in typical url |
1070 | | // encoding, that is %hh where h is a valid hex digit. It will also fold |
1071 | | // any duplicated slashes. |
1072 | | bool |
1073 | | nsUrlClassifierUtils::SpecialEncode(const nsACString & url, |
1074 | | bool foldSlashes, |
1075 | | nsACString & _retval) |
1076 | 0 | { |
1077 | 0 | bool changed = false; |
1078 | 0 | const char* curChar = url.BeginReading(); |
1079 | 0 | const char* end = url.EndReading(); |
1080 | 0 |
|
1081 | 0 | unsigned char lastChar = '\0'; |
1082 | 0 | while (curChar != end) { |
1083 | 0 | unsigned char c = static_cast<unsigned char>(*curChar); |
1084 | 0 | if (ShouldURLEscape(c)) { |
1085 | 0 | _retval.Append('%'); |
1086 | 0 | _retval.Append(int_to_hex_digit(c / 16)); |
1087 | 0 | _retval.Append(int_to_hex_digit(c % 16)); |
1088 | 0 |
|
1089 | 0 | changed = true; |
1090 | 0 | } else if (foldSlashes && (c == '/' && lastChar == '/')) { |
1091 | 0 | // skip |
1092 | 0 | } else { |
1093 | 0 | _retval.Append(*curChar); |
1094 | 0 | } |
1095 | 0 | lastChar = c; |
1096 | 0 | curChar++; |
1097 | 0 | } |
1098 | 0 | return changed; |
1099 | 0 | } |
1100 | | |
1101 | | bool |
1102 | | nsUrlClassifierUtils::ShouldURLEscape(const unsigned char c) const |
1103 | 0 | { |
1104 | 0 | return c <= 32 || c == '%' || c >=127; |
1105 | 0 | } |