Line data Source code
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 "nsEscape.h"
6 : #include "nsString.h"
7 : #include "nsIURI.h"
8 : #include "nsUrlClassifierUtils.h"
9 : #include "nsTArray.h"
10 : #include "nsReadableUtils.h"
11 : #include "plbase64.h"
12 : #include "nsPrintfCString.h"
13 : #include "safebrowsing.pb.h"
14 : #include "mozilla/Sprintf.h"
15 : #include "mozilla/Mutex.h"
16 :
17 : #define DEFAULT_PROTOCOL_VERSION "2.2"
18 :
19 0 : static char int_to_hex_digit(int32_t i)
20 : {
21 0 : NS_ASSERTION((i >= 0) && (i <= 15), "int too big in int_to_hex_digit");
22 0 : return static_cast<char>(((i < 10) ? (i + '0') : ((i - 10) + 'A')));
23 : }
24 :
25 : static bool
26 0 : IsDecimal(const nsACString & num)
27 : {
28 0 : for (uint32_t i = 0; i < num.Length(); i++) {
29 0 : if (!isdigit(num[i])) {
30 0 : return false;
31 : }
32 : }
33 :
34 0 : return true;
35 : }
36 :
37 : static bool
38 0 : IsHex(const nsACString & num)
39 : {
40 0 : if (num.Length() < 3) {
41 0 : return false;
42 : }
43 :
44 0 : if (num[0] != '0' || !(num[1] == 'x' || num[1] == 'X')) {
45 0 : return false;
46 : }
47 :
48 0 : for (uint32_t i = 2; i < num.Length(); i++) {
49 0 : if (!isxdigit(num[i])) {
50 0 : return false;
51 : }
52 : }
53 :
54 0 : return true;
55 : }
56 :
57 : static bool
58 0 : IsOctal(const nsACString & num)
59 : {
60 0 : if (num.Length() < 2) {
61 0 : return false;
62 : }
63 :
64 0 : if (num[0] != '0') {
65 0 : return false;
66 : }
67 :
68 0 : for (uint32_t i = 1; i < num.Length(); i++) {
69 0 : if (!isdigit(num[i]) || num[i] == '8' || num[i] == '9') {
70 0 : return false;
71 : }
72 : }
73 :
74 0 : return true;
75 : }
76 :
77 : /////////////////////////////////////////////////////////////////
78 : // SafeBrowsing V4 related utits.
79 :
80 : namespace mozilla {
81 : namespace safebrowsing {
82 :
83 : static PlatformType
84 0 : GetPlatformType()
85 : {
86 : #if defined(ANDROID)
87 : return ANDROID_PLATFORM;
88 : #elif defined(XP_MACOSX)
89 : return OSX_PLATFORM;
90 : #elif defined(XP_LINUX)
91 0 : return LINUX_PLATFORM;
92 : #elif defined(XP_WIN)
93 : return WINDOWS_PLATFORM;
94 : #else
95 : // Default to Linux for other platforms (see bug 1362501).
96 : return LINUX_PLATFORM;
97 : #endif
98 : }
99 :
100 : typedef FetchThreatListUpdatesRequest_ListUpdateRequest ListUpdateRequest;
101 : typedef FetchThreatListUpdatesRequest_ListUpdateRequest_Constraints Constraints;
102 :
103 : static void
104 0 : InitListUpdateRequest(ThreatType aThreatType,
105 : const char* aStateBase64,
106 : ListUpdateRequest* aListUpdateRequest)
107 : {
108 0 : aListUpdateRequest->set_threat_type(aThreatType);
109 0 : aListUpdateRequest->set_platform_type(GetPlatformType());
110 0 : aListUpdateRequest->set_threat_entry_type(URL);
111 :
112 0 : Constraints* contraints = new Constraints();
113 0 : contraints->add_supported_compressions(RICE);
114 0 : aListUpdateRequest->set_allocated_constraints(contraints);
115 :
116 : // Only set non-empty state.
117 0 : if (aStateBase64[0] != '\0') {
118 0 : nsCString stateBinary;
119 0 : nsresult rv = Base64Decode(nsDependentCString(aStateBase64), stateBinary);
120 0 : if (NS_SUCCEEDED(rv)) {
121 0 : aListUpdateRequest->set_state(stateBinary.get(), stateBinary.Length());
122 : }
123 : }
124 0 : }
125 :
126 : static ClientInfo*
127 0 : CreateClientInfo()
128 : {
129 0 : ClientInfo* c = new ClientInfo();
130 :
131 : nsCOMPtr<nsIPrefBranch> prefBranch =
132 0 : do_GetService(NS_PREFSERVICE_CONTRACTID);
133 :
134 0 : nsXPIDLCString clientId;
135 0 : nsresult rv = prefBranch->GetCharPref("browser.safebrowsing.id",
136 0 : getter_Copies(clientId));
137 :
138 0 : if (NS_FAILED(rv)) {
139 0 : clientId = "Firefox"; // Use "Firefox" as fallback.
140 : }
141 :
142 0 : c->set_client_id(clientId.get());
143 :
144 0 : return c;
145 : }
146 :
147 : } // end of namespace safebrowsing.
148 : } // end of namespace mozilla.
149 :
150 1 : nsUrlClassifierUtils::nsUrlClassifierUtils()
151 1 : : mProviderDictLock("nsUrlClassifierUtils.mProviderDictLock")
152 : {
153 1 : }
154 :
155 : nsresult
156 1 : nsUrlClassifierUtils::Init()
157 : {
158 : // nsIUrlClassifierUtils is a thread-safe service so it's
159 : // allowed to use on non-main threads. However, building
160 : // the provider dictionary must be on the main thread.
161 : // We forcefully load nsUrlClassifierUtils in
162 : // nsUrlClassifierDBService::Init() to ensure we must
163 : // now be on the main thread.
164 1 : nsresult rv = ReadProvidersFromPrefs(mProviderDict);
165 1 : NS_ENSURE_SUCCESS(rv, rv);
166 :
167 : // Add an observer for shutdown
168 : nsCOMPtr<nsIObserverService> observerService =
169 2 : mozilla::services::GetObserverService();
170 1 : if (!observerService)
171 0 : return NS_ERROR_FAILURE;
172 :
173 1 : observerService->AddObserver(this, "xpcom-shutdown-threads", false);
174 1 : Preferences::AddStrongObserver(this, "browser.safebrowsing");
175 :
176 1 : return NS_OK;
177 : }
178 :
179 159 : NS_IMPL_ISUPPORTS(nsUrlClassifierUtils,
180 : nsIUrlClassifierUtils,
181 : nsIObserver)
182 :
183 : /////////////////////////////////////////////////////////////////////////////
184 : // nsIUrlClassifierUtils
185 :
186 : NS_IMETHODIMP
187 1 : nsUrlClassifierUtils::GetKeyForURI(nsIURI * uri, nsACString & _retval)
188 : {
189 2 : nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
190 1 : if (!innerURI)
191 0 : innerURI = uri;
192 :
193 2 : nsAutoCString host;
194 1 : innerURI->GetAsciiHost(host);
195 :
196 1 : if (host.IsEmpty()) {
197 0 : return NS_ERROR_MALFORMED_URI;
198 : }
199 :
200 1 : nsresult rv = CanonicalizeHostname(host, _retval);
201 1 : NS_ENSURE_SUCCESS(rv, rv);
202 :
203 2 : nsAutoCString path;
204 1 : rv = innerURI->GetPath(path);
205 1 : NS_ENSURE_SUCCESS(rv, rv);
206 :
207 : // strip out anchors
208 1 : int32_t ref = path.FindChar('#');
209 1 : if (ref != kNotFound)
210 0 : path.SetLength(ref);
211 :
212 2 : nsAutoCString temp;
213 1 : rv = CanonicalizePath(path, temp);
214 1 : NS_ENSURE_SUCCESS(rv, rv);
215 :
216 1 : _retval.Append(temp);
217 :
218 1 : return NS_OK;
219 : }
220 :
221 : // We use "goog-*-proto" as the list name for v4, where "proto" indicates
222 : // it's updated (as well as hash completion) via protobuf.
223 : //
224 : // In the mozilla official build, we are allowed to use the
225 : // private phishing list (goog-phish-proto). See Bug 1288840.
226 : static const struct {
227 : const char* mListName;
228 : uint32_t mThreatType;
229 : } THREAT_TYPE_CONV_TABLE[] = {
230 : { "goog-malware-proto", MALWARE_THREAT}, // 1
231 : { "googpub-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2
232 : { "goog-unwanted-proto", UNWANTED_SOFTWARE}, // 3
233 : { "goog-phish-proto", SOCIAL_ENGINEERING}, // 5
234 :
235 : // For application reputation
236 : { "goog-badbinurl-proto", MALICIOUS_BINARY}, // 7
237 : { "goog-downloadwhite-proto", CSD_DOWNLOAD_WHITELIST}, // 9
238 :
239 : // For testing purpose.
240 : { "test-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2
241 : { "test-unwanted-proto", UNWANTED_SOFTWARE}, // 3
242 : };
243 :
244 : NS_IMETHODIMP
245 0 : nsUrlClassifierUtils::ConvertThreatTypeToListNames(uint32_t aThreatType,
246 : nsACString& aListNames)
247 : {
248 0 : for (uint32_t i = 0; i < ArrayLength(THREAT_TYPE_CONV_TABLE); i++) {
249 0 : if (aThreatType == THREAT_TYPE_CONV_TABLE[i].mThreatType) {
250 0 : if (!aListNames.IsEmpty()) {
251 0 : aListNames.AppendLiteral(",");
252 : }
253 0 : aListNames += THREAT_TYPE_CONV_TABLE[i].mListName;
254 : }
255 : }
256 :
257 0 : return aListNames.IsEmpty() ? NS_ERROR_FAILURE : NS_OK;
258 : }
259 :
260 : NS_IMETHODIMP
261 0 : nsUrlClassifierUtils::ConvertListNameToThreatType(const nsACString& aListName,
262 : uint32_t* aThreatType)
263 : {
264 0 : for (uint32_t i = 0; i < ArrayLength(THREAT_TYPE_CONV_TABLE); i++) {
265 0 : if (aListName.EqualsASCII(THREAT_TYPE_CONV_TABLE[i].mListName)) {
266 0 : *aThreatType = THREAT_TYPE_CONV_TABLE[i].mThreatType;
267 0 : return NS_OK;
268 : }
269 : }
270 :
271 0 : return NS_ERROR_FAILURE;
272 : }
273 :
274 : NS_IMETHODIMP
275 47 : nsUrlClassifierUtils::GetProvider(const nsACString& aTableName,
276 : nsACString& aProvider)
277 : {
278 94 : MutexAutoLock lock(mProviderDictLock);
279 47 : nsCString* provider = nullptr;
280 47 : if (StringBeginsWith(aTableName, NS_LITERAL_CSTRING("test"))) {
281 44 : aProvider = NS_LITERAL_CSTRING(TESTING_TABLE_PROVIDER_NAME);
282 3 : } else if (mProviderDict.Get(aTableName, &provider)) {
283 3 : aProvider = provider ? *provider : EmptyCString();
284 : } else {
285 0 : aProvider = EmptyCString();
286 : }
287 94 : return NS_OK;
288 : }
289 :
290 : NS_IMETHODIMP
291 4 : nsUrlClassifierUtils::GetTelemetryProvider(const nsACString& aTableName,
292 : nsACString& aProvider)
293 : {
294 4 : GetProvider(aTableName, aProvider);
295 : // Whitelist known providers to avoid reporting on private ones.
296 : // An empty provider is treated as "other"
297 20 : if (!NS_LITERAL_CSTRING("mozilla").Equals(aProvider) &&
298 12 : !NS_LITERAL_CSTRING("google").Equals(aProvider) &&
299 12 : !NS_LITERAL_CSTRING("google4").Equals(aProvider) &&
300 12 : !NS_LITERAL_CSTRING("baidu").Equals(aProvider) &&
301 12 : !NS_LITERAL_CSTRING("mozcn").Equals(aProvider) &&
302 24 : !NS_LITERAL_CSTRING("yandex").Equals(aProvider) &&
303 8 : !NS_LITERAL_CSTRING(TESTING_TABLE_PROVIDER_NAME).Equals(aProvider)) {
304 0 : aProvider.Assign(NS_LITERAL_CSTRING("other"));
305 : }
306 :
307 4 : return NS_OK;
308 : }
309 :
310 : NS_IMETHODIMP
311 0 : nsUrlClassifierUtils::GetProtocolVersion(const nsACString& aProvider,
312 : nsACString& aVersion)
313 : {
314 0 : nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
315 0 : if (prefBranch) {
316 : nsPrintfCString prefName("browser.safebrowsing.provider.%s.pver",
317 0 : nsCString(aProvider).get());
318 0 : nsXPIDLCString version;
319 0 : nsresult rv = prefBranch->GetCharPref(prefName.get(), getter_Copies(version));
320 :
321 0 : aVersion = NS_SUCCEEDED(rv) ? version : DEFAULT_PROTOCOL_VERSION;
322 : } else {
323 0 : aVersion = DEFAULT_PROTOCOL_VERSION;
324 : }
325 :
326 0 : return NS_OK;
327 : }
328 :
329 : NS_IMETHODIMP
330 0 : nsUrlClassifierUtils::MakeUpdateRequestV4(const char** aListNames,
331 : const char** aStatesBase64,
332 : uint32_t aCount,
333 : nsACString &aRequest)
334 : {
335 : using namespace mozilla::safebrowsing;
336 :
337 0 : FetchThreatListUpdatesRequest r;
338 0 : r.set_allocated_client(CreateClientInfo());
339 :
340 0 : for (uint32_t i = 0; i < aCount; i++) {
341 0 : nsCString listName(aListNames[i]);
342 : uint32_t threatType;
343 0 : nsresult rv = ConvertListNameToThreatType(listName, &threatType);
344 0 : if (NS_FAILED(rv)) {
345 0 : continue; // Unknown list name.
346 : }
347 0 : auto lur = r.mutable_list_update_requests()->Add();
348 0 : InitListUpdateRequest(static_cast<ThreatType>(threatType), aStatesBase64[i], lur);
349 : }
350 :
351 : // Then serialize.
352 0 : std::string s;
353 0 : r.SerializeToString(&s);
354 :
355 0 : nsCString out;
356 0 : nsresult rv = Base64URLEncode(s.size(),
357 0 : (const uint8_t*)s.c_str(),
358 : Base64URLEncodePaddingPolicy::Include,
359 0 : out);
360 0 : NS_ENSURE_SUCCESS(rv, rv);
361 :
362 0 : aRequest = out;
363 :
364 0 : return NS_OK;
365 : }
366 :
367 : NS_IMETHODIMP
368 0 : nsUrlClassifierUtils::MakeFindFullHashRequestV4(const char** aListNames,
369 : const char** aListStatesBase64,
370 : const char** aPrefixesBase64,
371 : uint32_t aListCount,
372 : uint32_t aPrefixCount,
373 : nsACString &aRequest)
374 : {
375 0 : FindFullHashesRequest r;
376 0 : r.set_allocated_client(CreateClientInfo());
377 :
378 : nsresult rv;
379 :
380 : // Set up FindFullHashesRequest.client_states.
381 0 : for (uint32_t i = 0; i < aListCount; i++) {
382 0 : nsCString stateBinary;
383 0 : rv = Base64Decode(nsDependentCString(aListStatesBase64[i]), stateBinary);
384 0 : NS_ENSURE_SUCCESS(rv, rv);
385 0 : r.add_client_states(stateBinary.get(), stateBinary.Length());
386 : }
387 :
388 : //-------------------------------------------------------------------
389 : // Set up FindFullHashesRequest.threat_info.
390 0 : auto threatInfo = r.mutable_threat_info();
391 :
392 : // 1) Set threat types.
393 0 : for (uint32_t i = 0; i < aListCount; i++) {
394 : uint32_t threatType;
395 0 : rv = ConvertListNameToThreatType(nsDependentCString(aListNames[i]), &threatType);
396 0 : NS_ENSURE_SUCCESS(rv, rv);
397 0 : threatInfo->add_threat_types((ThreatType)threatType);
398 : }
399 :
400 : // 2) Set platform type.
401 0 : threatInfo->add_platform_types(GetPlatformType());
402 :
403 : // 3) Set threat entry type.
404 0 : threatInfo->add_threat_entry_types(URL);
405 :
406 : // 4) Set threat entries.
407 0 : for (uint32_t i = 0; i < aPrefixCount; i++) {
408 0 : nsCString prefixBinary;
409 0 : rv = Base64Decode(nsDependentCString(aPrefixesBase64[i]), prefixBinary);
410 0 : threatInfo->add_threat_entries()->set_hash(prefixBinary.get(),
411 0 : prefixBinary.Length());
412 : }
413 : //-------------------------------------------------------------------
414 :
415 : // Then serialize.
416 0 : std::string s;
417 0 : r.SerializeToString(&s);
418 :
419 0 : nsCString out;
420 0 : rv = Base64URLEncode(s.size(),
421 0 : (const uint8_t*)s.c_str(),
422 : Base64URLEncodePaddingPolicy::Include,
423 0 : out);
424 0 : NS_ENSURE_SUCCESS(rv, rv);
425 :
426 0 : aRequest = out;
427 :
428 0 : return NS_OK;
429 : }
430 :
431 : static uint32_t
432 0 : DurationToMs(const Duration& aDuration)
433 : {
434 : // Seconds precision is good enough. Ignore nanoseconds like Chrome does.
435 0 : return aDuration.seconds() * 1000;
436 : }
437 :
438 : NS_IMETHODIMP
439 0 : nsUrlClassifierUtils::ParseFindFullHashResponseV4(const nsACString& aResponse,
440 : nsIUrlClassifierParseFindFullHashCallback *aCallback)
441 : {
442 : enum CompletionErrorType {
443 : SUCCESS = 0,
444 : PARSING_FAILURE = 1,
445 : UNKNOWN_THREAT_TYPE = 2,
446 : };
447 :
448 0 : FindFullHashesResponse r;
449 0 : if (!r.ParseFromArray(aResponse.BeginReading(), aResponse.Length())) {
450 0 : NS_WARNING("Invalid response");
451 : Telemetry::Accumulate(Telemetry::URLCLASSIFIER_COMPLETION_ERROR,
452 0 : PARSING_FAILURE);
453 0 : return NS_ERROR_FAILURE;
454 : }
455 :
456 0 : bool hasUnknownThreatType = false;
457 :
458 0 : for (auto& m : r.matches()) {
459 0 : nsCString tableNames;
460 0 : nsresult rv = ConvertThreatTypeToListNames(m.threat_type(), tableNames);
461 0 : if (NS_FAILED(rv)) {
462 0 : hasUnknownThreatType = true;
463 0 : continue; // Ignore un-convertable threat type.
464 : }
465 0 : auto& hash = m.threat().hash();
466 0 : auto cacheDurationSec = m.cache_duration().seconds();
467 0 : aCallback->OnCompleteHashFound(nsDependentCString(hash.c_str(), hash.length()),
468 0 : tableNames, cacheDurationSec);
469 :
470 0 : Telemetry::Accumulate(Telemetry::URLCLASSIFIER_POSITIVE_CACHE_DURATION,
471 0 : cacheDurationSec * PR_MSEC_PER_SEC);
472 : }
473 :
474 0 : auto minWaitDuration = DurationToMs(r.minimum_wait_duration());
475 0 : auto negCacheDurationSec = r.negative_cache_duration().seconds();
476 :
477 0 : aCallback->OnResponseParsed(minWaitDuration, negCacheDurationSec);
478 :
479 0 : Telemetry::Accumulate(Telemetry::URLCLASSIFIER_COMPLETION_ERROR,
480 0 : hasUnknownThreatType ? UNKNOWN_THREAT_TYPE : SUCCESS);
481 :
482 0 : Telemetry::Accumulate(Telemetry::URLCLASSIFIER_NEGATIVE_CACHE_DURATION,
483 0 : negCacheDurationSec * PR_MSEC_PER_SEC);
484 :
485 0 : return NS_OK;
486 : }
487 :
488 : //////////////////////////////////////////////////////////
489 : // nsIObserver
490 :
491 : NS_IMETHODIMP
492 0 : nsUrlClassifierUtils::Observe(nsISupports *aSubject, const char *aTopic,
493 : const char16_t *aData)
494 : {
495 0 : if (0 == strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
496 0 : MutexAutoLock lock(mProviderDictLock);
497 0 : return ReadProvidersFromPrefs(mProviderDict);
498 : }
499 :
500 0 : if (0 == strcmp(aTopic, "xpcom-shutdown-threads")) {
501 0 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
502 0 : NS_ENSURE_TRUE(prefs, NS_ERROR_FAILURE);
503 0 : return prefs->RemoveObserver("browser.safebrowsing", this);
504 : }
505 :
506 0 : return NS_ERROR_UNEXPECTED;
507 : }
508 :
509 : /////////////////////////////////////////////////////////////////////////////
510 : // non-interface methods
511 :
512 : nsresult
513 1 : nsUrlClassifierUtils::ReadProvidersFromPrefs(ProviderDictType& aDict)
514 : {
515 1 : MOZ_ASSERT(NS_IsMainThread(), "ReadProvidersFromPrefs must be on main thread");
516 :
517 2 : nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
518 1 : NS_ENSURE_TRUE(prefs, NS_ERROR_FAILURE);
519 2 : nsCOMPtr<nsIPrefBranch> prefBranch;
520 2 : nsresult rv = prefs->GetBranch("browser.safebrowsing.provider.",
521 2 : getter_AddRefs(prefBranch));
522 1 : NS_ENSURE_SUCCESS(rv, rv);
523 :
524 : // We've got a pref branch for "browser.safebrowsing.provider.".
525 : // Enumerate all children prefs and parse providers.
526 : uint32_t childCount;
527 : char** childArray;
528 1 : rv = prefBranch->GetChildList("", &childCount, &childArray);
529 1 : NS_ENSURE_SUCCESS(rv, rv);
530 :
531 : // Collect providers from childArray.
532 2 : nsTHashtable<nsCStringHashKey> providers;
533 24 : for (uint32_t i = 0; i < childCount; i++) {
534 46 : nsCString child(childArray[i]);
535 23 : auto dotPos = child.FindChar('.');
536 23 : if (dotPos < 0) {
537 0 : continue;
538 : }
539 :
540 46 : nsDependentCSubstring provider = Substring(child, 0, dotPos);
541 :
542 23 : providers.PutEntry(provider);
543 : }
544 1 : NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(childCount, childArray);
545 :
546 : // Now we have all providers. Check which one owns |aTableName|.
547 : // e.g. The owning lists of provider "google" is defined in
548 : // "browser.safebrowsing.provider.google.lists".
549 4 : for (auto itr = providers.Iter(); !itr.Done(); itr.Next()) {
550 3 : auto entry = itr.Get();
551 6 : nsCString provider(entry->GetKey());
552 6 : nsPrintfCString owninListsPref("%s.lists", provider.get());
553 :
554 6 : nsXPIDLCString owningLists;
555 6 : nsresult rv = prefBranch->GetCharPref(owninListsPref.get(),
556 6 : getter_Copies(owningLists));
557 3 : if (NS_FAILED(rv)) {
558 0 : continue;
559 : }
560 :
561 : // We've got the owning lists (represented as string) of |provider|.
562 : // Build the dictionary for the owning list and the current provider.
563 6 : nsTArray<nsCString> tables;
564 3 : Classifier::SplitTables(owningLists, tables);
565 27 : for (auto tableName : tables) {
566 24 : aDict.Put(tableName, new nsCString(provider));
567 : }
568 : }
569 :
570 1 : return NS_OK;
571 : }
572 :
573 : nsresult
574 1 : nsUrlClassifierUtils::CanonicalizeHostname(const nsACString & hostname,
575 : nsACString & _retval)
576 : {
577 2 : nsAutoCString unescaped;
578 2 : if (!NS_UnescapeURL(PromiseFlatCString(hostname).get(),
579 2 : PromiseFlatCString(hostname).Length(),
580 : 0, unescaped)) {
581 1 : unescaped.Assign(hostname);
582 : }
583 :
584 2 : nsAutoCString cleaned;
585 1 : CleanupHostname(unescaped, cleaned);
586 :
587 2 : nsAutoCString temp;
588 1 : ParseIPAddress(cleaned, temp);
589 1 : if (!temp.IsEmpty()) {
590 0 : cleaned.Assign(temp);
591 : }
592 :
593 1 : ToLowerCase(cleaned);
594 1 : SpecialEncode(cleaned, false, _retval);
595 :
596 2 : return NS_OK;
597 : }
598 :
599 :
600 : nsresult
601 1 : nsUrlClassifierUtils::CanonicalizePath(const nsACString & path,
602 : nsACString & _retval)
603 : {
604 1 : _retval.Truncate();
605 :
606 2 : nsAutoCString decodedPath(path);
607 2 : nsAutoCString temp;
608 1 : while (NS_UnescapeURL(decodedPath.get(), decodedPath.Length(), 0, temp)) {
609 0 : decodedPath.Assign(temp);
610 0 : temp.Truncate();
611 : }
612 :
613 1 : SpecialEncode(decodedPath, true, _retval);
614 : // XXX: lowercase the path?
615 :
616 2 : return NS_OK;
617 : }
618 :
619 : void
620 1 : nsUrlClassifierUtils::CleanupHostname(const nsACString & hostname,
621 : nsACString & _retval)
622 : {
623 1 : _retval.Truncate();
624 :
625 1 : const char* curChar = hostname.BeginReading();
626 1 : const char* end = hostname.EndReading();
627 1 : char lastChar = '\0';
628 19 : while (curChar != end) {
629 9 : unsigned char c = static_cast<unsigned char>(*curChar);
630 9 : if (c == '.' && (lastChar == '\0' || lastChar == '.')) {
631 : // skip
632 : } else {
633 9 : _retval.Append(*curChar);
634 : }
635 9 : lastChar = c;
636 9 : ++curChar;
637 : }
638 :
639 : // cut off trailing dots
640 0 : while (_retval.Length() > 0 && _retval[_retval.Length() - 1] == '.') {
641 0 : _retval.SetLength(_retval.Length() - 1);
642 : }
643 1 : }
644 :
645 : void
646 1 : nsUrlClassifierUtils::ParseIPAddress(const nsACString & host,
647 : nsACString & _retval)
648 : {
649 1 : _retval.Truncate();
650 1 : nsACString::const_iterator iter, end;
651 1 : host.BeginReading(iter);
652 1 : host.EndReading(end);
653 :
654 1 : if (host.Length() <= 15) {
655 : // The Windows resolver allows a 4-part dotted decimal IP address to
656 : // have a space followed by any old rubbish, so long as the total length
657 : // of the string doesn't get above 15 characters. So, "10.192.95.89 xy"
658 : // is resolved to 10.192.95.89.
659 : // If the string length is greater than 15 characters, e.g.
660 : // "10.192.95.89 xy.wildcard.example.com", it will be resolved through
661 : // DNS.
662 :
663 1 : if (FindCharInReadable(' ', iter, end)) {
664 0 : end = iter;
665 : }
666 : }
667 :
668 1 : for (host.BeginReading(iter); iter != end; iter++) {
669 1 : if (!(isxdigit(*iter) || *iter == 'x' || *iter == 'X' || *iter == '.')) {
670 : // not an IP
671 1 : return;
672 : }
673 : }
674 :
675 0 : host.BeginReading(iter);
676 0 : nsTArray<nsCString> parts;
677 0 : ParseString(PromiseFlatCString(Substring(iter, end)), '.', parts);
678 0 : if (parts.Length() > 4) {
679 0 : return;
680 : }
681 :
682 : // If any potentially-octal numbers (start with 0 but not hex) have
683 : // non-octal digits, no part of the ip can be in octal
684 : // XXX: this came from the old javascript implementation, is it really
685 : // supposed to be like this?
686 0 : bool allowOctal = true;
687 : uint32_t i;
688 :
689 0 : for (i = 0; i < parts.Length(); i++) {
690 0 : const nsCString& part = parts[i];
691 0 : if (part[0] == '0') {
692 0 : for (uint32_t j = 1; j < part.Length(); j++) {
693 0 : if (part[j] == 'x') {
694 0 : break;
695 : }
696 0 : if (part[j] == '8' || part[j] == '9') {
697 0 : allowOctal = false;
698 0 : break;
699 : }
700 : }
701 : }
702 : }
703 :
704 0 : for (i = 0; i < parts.Length(); i++) {
705 0 : nsAutoCString canonical;
706 :
707 0 : if (i == parts.Length() - 1) {
708 0 : CanonicalNum(parts[i], 5 - parts.Length(), allowOctal, canonical);
709 : } else {
710 0 : CanonicalNum(parts[i], 1, allowOctal, canonical);
711 : }
712 :
713 0 : if (canonical.IsEmpty()) {
714 0 : _retval.Truncate();
715 0 : return;
716 : }
717 :
718 0 : if (_retval.IsEmpty()) {
719 0 : _retval.Assign(canonical);
720 : } else {
721 0 : _retval.Append('.');
722 0 : _retval.Append(canonical);
723 : }
724 : }
725 0 : return;
726 : }
727 :
728 : void
729 0 : nsUrlClassifierUtils::CanonicalNum(const nsACString& num,
730 : uint32_t bytes,
731 : bool allowOctal,
732 : nsACString& _retval)
733 : {
734 0 : _retval.Truncate();
735 :
736 0 : if (num.Length() < 1) {
737 0 : return;
738 : }
739 :
740 : uint32_t val;
741 0 : if (allowOctal && IsOctal(num)) {
742 0 : if (PR_sscanf(PromiseFlatCString(num).get(), "%o", &val) != 1) {
743 0 : return;
744 : }
745 0 : } else if (IsDecimal(num)) {
746 0 : if (PR_sscanf(PromiseFlatCString(num).get(), "%u", &val) != 1) {
747 0 : return;
748 : }
749 0 : } else if (IsHex(num)) {
750 0 : if (PR_sscanf(PromiseFlatCString(num).get(), num[1] == 'X' ? "0X%x" : "0x%x",
751 : &val) != 1) {
752 0 : return;
753 : }
754 : } else {
755 0 : return;
756 : }
757 :
758 0 : while (bytes--) {
759 : char buf[20];
760 0 : SprintfLiteral(buf, "%u", val & 0xff);
761 0 : if (_retval.IsEmpty()) {
762 0 : _retval.Assign(buf);
763 : } else {
764 0 : _retval = nsDependentCString(buf) + NS_LITERAL_CSTRING(".") + _retval;
765 : }
766 0 : val >>= 8;
767 : }
768 : }
769 :
770 : // This function will encode all "special" characters in typical url
771 : // encoding, that is %hh where h is a valid hex digit. It will also fold
772 : // any duplicated slashes.
773 : bool
774 2 : nsUrlClassifierUtils::SpecialEncode(const nsACString & url,
775 : bool foldSlashes,
776 : nsACString & _retval)
777 : {
778 2 : bool changed = false;
779 2 : const char* curChar = url.BeginReading();
780 2 : const char* end = url.EndReading();
781 :
782 2 : unsigned char lastChar = '\0';
783 44 : while (curChar != end) {
784 21 : unsigned char c = static_cast<unsigned char>(*curChar);
785 21 : if (ShouldURLEscape(c)) {
786 0 : _retval.Append('%');
787 0 : _retval.Append(int_to_hex_digit(c / 16));
788 0 : _retval.Append(int_to_hex_digit(c % 16));
789 :
790 0 : changed = true;
791 21 : } else if (foldSlashes && (c == '/' && lastChar == '/')) {
792 : // skip
793 : } else {
794 21 : _retval.Append(*curChar);
795 : }
796 21 : lastChar = c;
797 21 : curChar++;
798 : }
799 2 : return changed;
800 : }
801 :
802 : bool
803 21 : nsUrlClassifierUtils::ShouldURLEscape(const unsigned char c) const
804 : {
805 21 : return c <= 32 || c == '%' || c >=127;
806 : }
|