ReportingHeader.cpp (25103B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/ReportingHeader.h" 8 9 #include <limits> 10 11 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject 12 #include "js/JSON.h" 13 #include "js/PropertyAndElement.h" // JS_GetElement 14 #include "mozilla/OriginAttributes.h" 15 #include "mozilla/Services.h" 16 #include "mozilla/StaticPrefs_dom.h" 17 #include "mozilla/StaticPtr.h" 18 #include "mozilla/dom/ReportingBinding.h" 19 #include "mozilla/dom/ScriptSettings.h" 20 #include "mozilla/dom/SimpleGlobalObject.h" 21 #include "mozilla/ipc/BackgroundUtils.h" 22 #include "mozilla/net/SFVService.h" 23 #include "nsCOMPtr.h" 24 #include "nsContentUtils.h" 25 #include "nsIEffectiveTLDService.h" 26 #include "nsIHttpChannel.h" 27 #include "nsIHttpProtocolHandler.h" 28 #include "nsIObserverService.h" 29 #include "nsIPrincipal.h" 30 #include "nsIRandomGenerator.h" 31 #include "nsIScriptError.h" 32 #include "nsNetUtil.h" 33 #include "nsXULAppAPI.h" 34 35 #define REPORTING_PURGE_ALL "reporting:purge-all" 36 #define REPORTING_PURGE_HOST "reporting:purge-host" 37 38 namespace mozilla::dom { 39 40 namespace { 41 42 StaticRefPtr<ReportingHeader> gReporting; 43 44 } // namespace 45 46 /* static */ 47 void ReportingHeader::Initialize() { 48 MOZ_ASSERT(!gReporting); 49 MOZ_ASSERT(NS_IsMainThread()); 50 51 if (!XRE_IsParentProcess()) { 52 return; 53 } 54 55 RefPtr<ReportingHeader> service = new ReportingHeader(); 56 57 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 58 if (NS_WARN_IF(!obs)) { 59 return; 60 } 61 62 obs->AddObserver(service, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC, false); 63 obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 64 obs->AddObserver(service, "clear-origin-attributes-data", false); 65 obs->AddObserver(service, REPORTING_PURGE_HOST, false); 66 obs->AddObserver(service, REPORTING_PURGE_ALL, false); 67 68 gReporting = service; 69 } 70 71 /* static */ 72 void ReportingHeader::Shutdown() { 73 MOZ_ASSERT(NS_IsMainThread()); 74 75 if (!gReporting) { 76 return; 77 } 78 79 RefPtr<ReportingHeader> service = gReporting; 80 gReporting = nullptr; 81 82 if (service->mCleanupTimer) { 83 service->mCleanupTimer->Cancel(); 84 service->mCleanupTimer = nullptr; 85 } 86 87 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 88 if (NS_WARN_IF(!obs)) { 89 return; 90 } 91 92 obs->RemoveObserver(service, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC); 93 obs->RemoveObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 94 obs->RemoveObserver(service, "clear-origin-attributes-data"); 95 obs->RemoveObserver(service, REPORTING_PURGE_HOST); 96 obs->RemoveObserver(service, REPORTING_PURGE_ALL); 97 } 98 99 ReportingHeader::ReportingHeader() = default; 100 ReportingHeader::~ReportingHeader() = default; 101 102 NS_IMETHODIMP 103 ReportingHeader::Observe(nsISupports* aSubject, const char* aTopic, 104 const char16_t* aData) { 105 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 106 Shutdown(); 107 return NS_OK; 108 } 109 110 // Pref disabled. 111 if (!StaticPrefs::dom_reporting_header_enabled()) { 112 return NS_OK; 113 } 114 115 if (!strcmp(aTopic, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC)) { 116 nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aSubject); 117 if (NS_WARN_IF(!channel)) { 118 return NS_OK; 119 } 120 121 ReportingFromChannel(channel); 122 return NS_OK; 123 } 124 125 if (!strcmp(aTopic, REPORTING_PURGE_HOST)) { 126 RemoveOriginsFromHost(nsDependentString(aData)); 127 return NS_OK; 128 } 129 130 if (!strcmp(aTopic, "clear-origin-attributes-data")) { 131 OriginAttributesPattern pattern; 132 if (!pattern.Init(nsDependentString(aData))) { 133 NS_ERROR("Cannot parse origin attributes pattern"); 134 return NS_ERROR_FAILURE; 135 } 136 137 RemoveOriginsFromOriginAttributesPattern(pattern); 138 return NS_OK; 139 } 140 141 if (!strcmp(aTopic, REPORTING_PURGE_ALL)) { 142 RemoveOrigins(); 143 return NS_OK; 144 } 145 146 return NS_ERROR_FAILURE; 147 } 148 149 void ReportingHeader::ReportingFromChannel(nsIHttpChannel* aChannel) { 150 MOZ_ASSERT(aChannel); 151 152 if (!StaticPrefs::dom_reporting_header_enabled()) { 153 return; 154 } 155 156 // We want to use the final URI to check if Report-To should be allowed or 157 // not. 158 nsCOMPtr<nsIURI> uri; 159 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 160 if (NS_WARN_IF(NS_FAILED(rv))) { 161 return; 162 } 163 164 if (!IsSecureURI(uri)) { 165 return; 166 } 167 168 if (NS_UsePrivateBrowsing(aChannel)) { 169 return; 170 } 171 172 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 173 if (NS_WARN_IF(!ssm)) { 174 return; 175 } 176 177 nsCOMPtr<nsIPrincipal> principal; 178 rv = ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(principal)); 179 if (NS_WARN_IF(NS_FAILED(rv)) || !principal) { 180 return; 181 } 182 183 nsAutoCString origin; 184 rv = principal->GetOrigin(origin); 185 if (NS_WARN_IF(NS_FAILED(rv))) { 186 return; 187 } 188 189 // Parse Report-To and Reporting-Endpoints headers 190 UniquePtr<Client> client; 191 nsAutoCString header; 192 193 if (NS_SUCCEEDED( 194 aChannel->GetResponseHeader("Reporting-Endpoints"_ns, header))) { 195 client = ParseReportingEndpointsHeader(header, uri); 196 } else if (NS_SUCCEEDED( 197 aChannel->GetResponseHeader("Report-To"_ns, header))) { 198 client = ParseReportToHeader(aChannel, uri, header); 199 } 200 201 if (!client) { 202 return; 203 } 204 205 // Here we override the previous data. 206 mOrigins.InsertOrUpdate(origin, std::move(client)); 207 208 MaybeCreateCleanupTimer(); 209 } 210 211 /* static */ 212 UniquePtr<ReportingHeader::Client> 213 ReportingHeader::ParseReportingEndpointsHeader(const nsACString& aHeaderValue, 214 nsIURI* aURI) { 215 nsCOMPtr<nsISFVService> sfv = mozilla::net::GetSFVService(); 216 217 nsAutoCString uriSpec; 218 aURI->GetSpec(uriSpec); 219 220 nsCOMPtr<nsIURI> baseURL; 221 if (NS_FAILED(NS_NewURI(getter_AddRefs(baseURL), uriSpec))) { 222 return nullptr; 223 } 224 225 nsCOMPtr<nsISFVDictionary> parsedHeader; 226 if (NS_FAILED( 227 sfv->ParseDictionary(aHeaderValue, getter_AddRefs(parsedHeader)))) { 228 return nullptr; 229 } 230 231 nsTArray<nsCString> keys; 232 if (NS_FAILED(parsedHeader->Keys(keys))) { 233 return nullptr; 234 } 235 236 UniquePtr<Client> client = MakeUnique<Client>(); 237 238 for (const auto& key : keys) { 239 // Extract an SFV data object from each dictionary entry 240 nsCOMPtr<nsISFVItemOrInnerList> iil; 241 if (NS_FAILED(parsedHeader->Get(key, getter_AddRefs(iil)))) { 242 continue; 243 } 244 245 // An item needs to be extracted from the ItemOrInnerList member-value 246 nsCOMPtr<nsISFVBareItem> value; 247 if (nsCOMPtr<nsISFVInnerList> innerList = do_QueryInterface(iil)) { 248 // Extract the first entry of each inner list, which should contain the 249 // endpoint's URL string 250 nsTArray<RefPtr<nsISFVItem>> items; 251 252 if (NS_FAILED(innerList->GetItems(items))) { 253 continue; 254 } 255 256 if (items.IsEmpty()) { 257 continue; 258 } 259 260 nsCOMPtr<nsISFVItem> firstItem(items[0]); 261 262 if (NS_FAILED(firstItem->GetValue(getter_AddRefs(value)))) { 263 continue; 264 } 265 } else if (nsCOMPtr<nsISFVItem> listItem = do_QueryInterface(iil)) { 266 if (NS_FAILED(listItem->GetValue(getter_AddRefs(value)))) { 267 continue; 268 } 269 } 270 271 // Ensure that the item's data type is a string, so the URL can be properly 272 // parsed 273 nsCOMPtr<nsISFVString> sfvString(do_QueryInterface(value)); 274 if (!sfvString) { 275 continue; 276 } 277 278 nsAutoCString endpointURLString; 279 if (NS_FAILED(sfvString->GetValue(endpointURLString))) { 280 continue; 281 } 282 283 // Convert the URL string into a URI 284 nsCOMPtr<nsIURI> endpointURL; 285 nsresult rv = NS_NewURI(getter_AddRefs(endpointURL), 286 endpointURLString.get(), baseURL); 287 if (NS_WARN_IF(NS_FAILED(rv))) { 288 continue; 289 } 290 291 if (!IsSecureURI(endpointURL)) { 292 continue; 293 } 294 295 Group* group = client->mGroups.AppendElement(); 296 group->mCreationTime = TimeStamp::Now(); 297 group->mTTL = std::numeric_limits<int32_t>::max(); 298 group->mName = NS_ConvertUTF8toUTF16(key); 299 300 // Use data extracted from dictionary entry to create an endpoint 301 Endpoint* ep = group->mEndpoints.AppendElement(); 302 ep->mUrl = endpointURL; 303 ep->mEndpointName = key; 304 ep->mFailures = 0; 305 ep->mPriority = 1; 306 ep->mWeight = 1; 307 } 308 309 if (client->mGroups.IsEmpty()) { 310 return nullptr; 311 } 312 313 return client; 314 } 315 316 /* static */ UniquePtr<ReportingHeader::Client> 317 ReportingHeader::ParseReportToHeader(nsIHttpChannel* aChannel, nsIURI* aURI, 318 const nsACString& aHeaderValue) { 319 MOZ_ASSERT(aURI); 320 // aChannel can be null in gtest 321 322 AutoJSAPI jsapi; 323 324 JSObject* cleanGlobal = 325 SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::BindingDetail); 326 if (NS_WARN_IF(!cleanGlobal)) { 327 return nullptr; 328 } 329 330 if (NS_WARN_IF(!jsapi.Init(cleanGlobal))) { 331 return nullptr; 332 } 333 334 // WebIDL dictionary parses single items. Let's create a object to parse the 335 // header. 336 nsAutoString json; 337 json.AppendASCII("{ \"items\": ["); 338 json.Append(NS_ConvertUTF8toUTF16(aHeaderValue)); 339 json.AppendASCII("]}"); 340 341 JSContext* cx = jsapi.cx(); 342 JS::Rooted<JS::Value> jsonValue(cx); 343 bool ok = JS_ParseJSON(cx, json.BeginReading(), json.Length(), &jsonValue); 344 if (!ok) { 345 LogToConsoleInvalidJSON(aChannel, aURI); 346 return nullptr; 347 } 348 349 dom::ReportingHeaderValue data; 350 if (!data.Init(cx, jsonValue)) { 351 LogToConsoleInvalidJSON(aChannel, aURI); 352 return nullptr; 353 } 354 355 if (!data.mItems.WasPassed() || data.mItems.Value().IsEmpty()) { 356 return nullptr; 357 } 358 359 UniquePtr<Client> client = MakeUnique<Client>(); 360 361 for (const dom::ReportingItem& item : data.mItems.Value()) { 362 nsAutoString groupName; 363 364 if (item.mGroup.isUndefined()) { 365 groupName.AssignLiteral("default"); 366 } else if (!item.mGroup.isString()) { 367 LogToConsoleInvalidNameItem(aChannel, aURI); 368 continue; 369 } else { 370 JS::Rooted<JSString*> groupStr(cx, item.mGroup.toString()); 371 MOZ_ASSERT(groupStr); 372 373 nsAutoJSString string; 374 if (NS_WARN_IF(!string.init(cx, groupStr))) { 375 continue; 376 } 377 378 groupName = string; 379 } 380 381 if (!item.mMax_age.isNumber() || !item.mEndpoints.isObject()) { 382 LogToConsoleIncompleteItem(aChannel, aURI, groupName); 383 continue; 384 } 385 386 JS::Rooted<JSObject*> endpoints(cx, &item.mEndpoints.toObject()); 387 MOZ_ASSERT(endpoints); 388 389 bool isArray = false; 390 if (!JS::IsArrayObject(cx, endpoints, &isArray) || !isArray) { 391 LogToConsoleIncompleteItem(aChannel, aURI, groupName); 392 continue; 393 } 394 395 uint32_t endpointsLength; 396 if (!JS::GetArrayLength(cx, endpoints, &endpointsLength) || 397 endpointsLength == 0) { 398 // TODO: should this clear the endpoint instead? 399 LogToConsoleIncompleteItem(aChannel, aURI, groupName); 400 continue; 401 } 402 403 const auto [begin, end] = client->mGroups.NonObservingRange(); 404 if (std::any_of(begin, end, [&groupName](const Group& group) { 405 return group.mName == groupName; 406 })) { 407 LogToConsoleDuplicateGroup(aChannel, aURI, groupName); 408 continue; 409 } 410 411 Group* group = client->mGroups.AppendElement(); 412 group->mName = groupName; 413 group->mIncludeSubdomains = item.mInclude_subdomains; 414 group->mTTL = item.mMax_age.toNumber(); 415 group->mCreationTime = TimeStamp::Now(); 416 417 for (uint32_t i = 0; i < endpointsLength; ++i) { 418 JS::Rooted<JS::Value> element(cx); 419 if (!JS_GetElement(cx, endpoints, i, &element)) { 420 return nullptr; 421 } 422 423 RootedDictionary<ReportingEndpoint> endpoint(cx); 424 if (!endpoint.Init(cx, element)) { 425 LogToConsoleIncompleteEndpoint(aChannel, aURI, groupName); 426 continue; 427 } 428 429 if (!endpoint.mUrl.isString() || 430 (!endpoint.mPriority.isUndefined() && 431 (!endpoint.mPriority.isNumber() || 432 endpoint.mPriority.toNumber() < 0)) || 433 (!endpoint.mWeight.isUndefined() && 434 (!endpoint.mWeight.isNumber() || endpoint.mWeight.toNumber() < 0))) { 435 LogToConsoleIncompleteEndpoint(aChannel, aURI, groupName); 436 continue; 437 } 438 439 JS::Rooted<JSString*> endpointUrl(cx, endpoint.mUrl.toString()); 440 MOZ_ASSERT(endpointUrl); 441 442 nsAutoJSString endpointString; 443 if (NS_WARN_IF(!endpointString.init(cx, endpointUrl))) { 444 continue; 445 } 446 447 nsCOMPtr<nsIURI> uri; 448 nsresult rv = NS_NewURI(getter_AddRefs(uri), endpointString); 449 if (NS_FAILED(rv)) { 450 LogToConsoleInvalidURLEndpoint(aChannel, aURI, groupName, 451 endpointString); 452 continue; 453 } 454 455 Endpoint* ep = group->mEndpoints.AppendElement(); 456 ep->mUrl = uri; 457 ep->mPriority = 458 endpoint.mPriority.isUndefined() ? 1 : endpoint.mPriority.toNumber(); 459 ep->mWeight = 460 endpoint.mWeight.isUndefined() ? 1 : endpoint.mWeight.toNumber(); 461 } 462 } 463 464 if (client->mGroups.IsEmpty()) { 465 return nullptr; 466 } 467 468 return client; 469 } 470 471 /* static */ 472 bool ReportingHeader::IsSecureURI(nsIURI* aURI) { 473 MOZ_ASSERT(aURI); 474 475 bool prioriAuthenticated = false; 476 if (NS_WARN_IF(NS_FAILED(NS_URIChainHasFlags( 477 aURI, nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY, 478 &prioriAuthenticated)))) { 479 return false; 480 } 481 482 return prioriAuthenticated; 483 } 484 485 /* static */ 486 void ReportingHeader::LogToConsoleInvalidJSON(nsIHttpChannel* aChannel, 487 nsIURI* aURI) { 488 nsTArray<nsString> params; 489 LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidJSON", params); 490 } 491 492 /* static */ 493 void ReportingHeader::LogToConsoleDuplicateGroup(nsIHttpChannel* aChannel, 494 nsIURI* aURI, 495 const nsAString& aName) { 496 nsTArray<nsString> params; 497 params.AppendElement(aName); 498 499 LogToConsoleInternal(aChannel, aURI, "ReportingHeaderDuplicateGroup", params); 500 } 501 502 /* static */ 503 void ReportingHeader::LogToConsoleInvalidNameItem(nsIHttpChannel* aChannel, 504 nsIURI* aURI) { 505 nsTArray<nsString> params; 506 LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidNameItem", 507 params); 508 } 509 510 /* static */ 511 void ReportingHeader::LogToConsoleIncompleteItem(nsIHttpChannel* aChannel, 512 nsIURI* aURI, 513 const nsAString& aName) { 514 nsTArray<nsString> params; 515 params.AppendElement(aName); 516 517 LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidItem", params); 518 } 519 520 /* static */ 521 void ReportingHeader::LogToConsoleIncompleteEndpoint(nsIHttpChannel* aChannel, 522 nsIURI* aURI, 523 const nsAString& aName) { 524 nsTArray<nsString> params; 525 params.AppendElement(aName); 526 527 LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidEndpoint", 528 params); 529 } 530 531 /* static */ 532 void ReportingHeader::LogToConsoleInvalidURLEndpoint(nsIHttpChannel* aChannel, 533 nsIURI* aURI, 534 const nsAString& aName, 535 const nsAString& aURL) { 536 nsTArray<nsString> params; 537 params.AppendElement(aURL); 538 params.AppendElement(aName); 539 540 LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidURLEndpoint", 541 params); 542 } 543 544 /* static */ 545 void ReportingHeader::LogToConsoleInternal(nsIHttpChannel* aChannel, 546 nsIURI* aURI, const char* aMsg, 547 const nsTArray<nsString>& aParams) { 548 MOZ_ASSERT(aURI); 549 550 if (!aChannel) { 551 // We are in a gtest. 552 return; 553 } 554 555 uint64_t windowID = 0; 556 557 nsresult rv = aChannel->GetTopLevelContentWindowId(&windowID); 558 if (NS_WARN_IF(NS_FAILED(rv))) { 559 return; 560 } 561 562 if (!windowID) { 563 nsCOMPtr<nsILoadGroup> loadGroup; 564 nsresult rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); 565 if (NS_WARN_IF(NS_FAILED(rv))) { 566 return; 567 } 568 569 if (loadGroup) { 570 windowID = nsContentUtils::GetInnerWindowID(loadGroup); 571 } 572 } 573 574 nsAutoString localizedMsg; 575 rv = nsContentUtils::FormatLocalizedString( 576 nsContentUtils::eSECURITY_PROPERTIES, aMsg, aParams, localizedMsg); 577 if (NS_WARN_IF(NS_FAILED(rv))) { 578 return; 579 } 580 581 rv = nsContentUtils::ReportToConsoleByWindowID( 582 localizedMsg, nsIScriptError::infoFlag, "Reporting"_ns, windowID, 583 SourceLocation(aURI)); 584 (void)NS_WARN_IF(NS_FAILED(rv)); 585 } 586 587 /* static */ 588 void ReportingHeader::GetEndpointForReport( 589 const nsAString& aGroupName, 590 const mozilla::ipc::PrincipalInfo& aPrincipalInfo, 591 nsACString& aEndpointURI) { 592 auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo); 593 if (NS_WARN_IF(principalOrErr.isErr())) { 594 return; 595 } 596 597 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); 598 GetEndpointForReport(aGroupName, principal, aEndpointURI); 599 } 600 601 /* static */ 602 void ReportingHeader::GetEndpointForReport(const nsAString& aGroupName, 603 nsIPrincipal* aPrincipal, 604 nsACString& aEndpointURI) { 605 return GetEndpointForReportIncludeSubdomains( 606 aGroupName, aPrincipal, /* includeSubdomains */ false, aEndpointURI); 607 } 608 /* static */ 609 void ReportingHeader::GetEndpointForReportIncludeSubdomains( 610 const nsAString& aGroupName, nsIPrincipal* aPrincipal, 611 bool aIncludeSubdomains, nsACString& aEndpointURI) { 612 MOZ_ASSERT(aEndpointURI.IsEmpty()); 613 614 if (!gReporting) { 615 return; 616 } 617 618 nsCOMPtr<nsIPrincipal> principal = aPrincipal; 619 bool mustHaveIncludeSubdomains = false; 620 621 do { 622 nsAutoCString origin; 623 nsresult rv = principal->GetOrigin(origin); 624 if (NS_WARN_IF(NS_FAILED(rv))) { 625 return; 626 } 627 628 Client* client = gReporting->mOrigins.Get(origin); 629 if (client) { 630 const auto [begin, end] = client->mGroups.NonObservingRange(); 631 const auto foundIt = std::find_if( 632 begin, end, 633 [&aGroupName, mustHaveIncludeSubdomains](const Group& group) { 634 return group.mName == aGroupName && 635 (!mustHaveIncludeSubdomains || group.mIncludeSubdomains); 636 }); 637 if (foundIt != end) { 638 GetEndpointForReportInternal(*foundIt, aEndpointURI); 639 return; 640 } 641 } 642 643 nsCOMPtr<nsIPrincipal> oldPrincipal = std::move(principal); 644 oldPrincipal->GetNextSubDomainPrincipal(getter_AddRefs(principal)); 645 mustHaveIncludeSubdomains = true; 646 } while (principal && aIncludeSubdomains); 647 648 // XXX More explicitly report an error if not found? 649 } 650 651 /* static */ 652 void ReportingHeader::GetEndpointForReportInternal( 653 const ReportingHeader::Group& aGroup, nsACString& aEndpointURI) { 654 TimeDuration diff = TimeStamp::Now() - aGroup.mCreationTime; 655 if (diff.ToSeconds() > aGroup.mTTL) { 656 // Expired. 657 return; 658 } 659 660 if (aGroup.mEndpoints.IsEmpty()) { 661 return; 662 } 663 664 int64_t minPriority = -1; 665 uint32_t totalWeight = 0; 666 667 for (const Endpoint& endpoint : aGroup.mEndpoints.NonObservingRange()) { 668 if (minPriority == -1 || minPriority > endpoint.mPriority) { 669 minPriority = endpoint.mPriority; 670 totalWeight = endpoint.mWeight; 671 } else if (minPriority == endpoint.mPriority) { 672 totalWeight += endpoint.mWeight; 673 } 674 } 675 676 nsCOMPtr<nsIRandomGenerator> randomGenerator = 677 do_GetService("@mozilla.org/security/random-generator;1"); 678 if (NS_WARN_IF(!randomGenerator)) { 679 return; 680 } 681 682 uint32_t randomNumber = 0; 683 684 nsresult rv = randomGenerator->GenerateRandomBytesInto(randomNumber); 685 if (NS_WARN_IF(NS_FAILED(rv))) { 686 return; 687 } 688 689 totalWeight = randomNumber % totalWeight; 690 691 const auto [begin, end] = aGroup.mEndpoints.NonObservingRange(); 692 const auto foundIt = std::find_if( 693 begin, end, [minPriority, totalWeight](const Endpoint& endpoint) { 694 return minPriority == endpoint.mPriority && 695 totalWeight < endpoint.mWeight; 696 }); 697 if (foundIt != end) { 698 (void)NS_WARN_IF(NS_FAILED(foundIt->mUrl->GetSpec(aEndpointURI))); 699 } 700 // XXX More explicitly report an error if not found? 701 } 702 703 /* static */ 704 void ReportingHeader::RemoveEndpoint( 705 const nsAString& aGroupName, const nsACString& aEndpointURL, 706 const mozilla::ipc::PrincipalInfo& aPrincipalInfo) { 707 if (!gReporting) { 708 return; 709 } 710 711 nsCOMPtr<nsIURI> uri; 712 nsresult rv = NS_NewURI(getter_AddRefs(uri), aEndpointURL); 713 if (NS_WARN_IF(NS_FAILED(rv))) { 714 return; 715 } 716 717 auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo); 718 if (NS_WARN_IF(principalOrErr.isErr())) { 719 return; 720 } 721 722 nsAutoCString origin; 723 rv = principalOrErr.unwrap()->GetOrigin(origin); 724 if (NS_WARN_IF(NS_FAILED(rv))) { 725 return; 726 } 727 728 Client* client = gReporting->mOrigins.Get(origin); 729 if (!client) { 730 return; 731 } 732 733 // Scope for the group iterator. 734 { 735 nsTObserverArray<Group>::BackwardIterator iter(client->mGroups); 736 while (iter.HasMore()) { 737 const Group& group = iter.GetNext(); 738 if (group.mName != aGroupName) { 739 continue; 740 } 741 742 // Scope for the endpoint iterator. 743 { 744 nsTObserverArray<Endpoint>::BackwardIterator endpointIter( 745 group.mEndpoints); 746 while (endpointIter.HasMore()) { 747 const Endpoint& endpoint = endpointIter.GetNext(); 748 749 bool equal = false; 750 rv = endpoint.mUrl->Equals(uri, &equal); 751 if (NS_WARN_IF(NS_FAILED(rv))) { 752 continue; 753 } 754 755 if (equal) { 756 endpointIter.Remove(); 757 break; 758 } 759 } 760 } 761 762 if (group.mEndpoints.IsEmpty()) { 763 iter.Remove(); 764 } 765 766 break; 767 } 768 } 769 770 if (client->mGroups.IsEmpty()) { 771 gReporting->mOrigins.Remove(origin); 772 gReporting->MaybeCancelCleanupTimer(); 773 } 774 } 775 776 void ReportingHeader::RemoveOriginsFromHost(const nsAString& aHost) { 777 nsCOMPtr<nsIEffectiveTLDService> tldService = 778 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); 779 if (NS_WARN_IF(!tldService)) { 780 return; 781 } 782 783 NS_ConvertUTF16toUTF8 host(aHost); 784 785 for (auto iter = mOrigins.Iter(); !iter.Done(); iter.Next()) { 786 bool hasRootDomain = false; 787 nsresult rv = tldService->HasRootDomain(iter.Key(), host, &hasRootDomain); 788 if (NS_WARN_IF(NS_FAILED(rv)) || !hasRootDomain) { 789 continue; 790 } 791 792 iter.Remove(); 793 } 794 795 MaybeCancelCleanupTimer(); 796 } 797 798 void ReportingHeader::RemoveOriginsFromOriginAttributesPattern( 799 const OriginAttributesPattern& aPattern) { 800 for (auto iter = mOrigins.Iter(); !iter.Done(); iter.Next()) { 801 nsAutoCString suffix; 802 OriginAttributes attr; 803 if (NS_WARN_IF(!attr.PopulateFromOrigin(iter.Key(), suffix))) { 804 continue; 805 } 806 807 if (aPattern.Matches(attr)) { 808 iter.Remove(); 809 } 810 } 811 812 MaybeCancelCleanupTimer(); 813 } 814 815 void ReportingHeader::RemoveOrigins() { 816 mOrigins.Clear(); 817 MaybeCancelCleanupTimer(); 818 } 819 820 void ReportingHeader::RemoveOriginsForTTL() { 821 TimeStamp now = TimeStamp::Now(); 822 823 for (auto iter = mOrigins.Iter(); !iter.Done(); iter.Next()) { 824 Client* client = iter.UserData(); 825 826 // Scope of the iterator. 827 { 828 nsTObserverArray<Group>::BackwardIterator groupIter(client->mGroups); 829 while (groupIter.HasMore()) { 830 const Group& group = groupIter.GetNext(); 831 TimeDuration diff = now - group.mCreationTime; 832 if (diff.ToSeconds() > group.mTTL) { 833 groupIter.Remove(); 834 return; 835 } 836 } 837 } 838 839 if (client->mGroups.IsEmpty()) { 840 iter.Remove(); 841 } 842 } 843 } 844 845 /* static */ 846 bool ReportingHeader::HasReportingHeaderForOrigin(const nsACString& aOrigin) { 847 if (!gReporting) { 848 return false; 849 } 850 851 return gReporting->mOrigins.Contains(aOrigin); 852 } 853 854 NS_IMETHODIMP 855 ReportingHeader::Notify(nsITimer* aTimer) { 856 mCleanupTimer = nullptr; 857 858 RemoveOriginsForTTL(); 859 MaybeCreateCleanupTimer(); 860 861 return NS_OK; 862 } 863 864 NS_IMETHODIMP 865 ReportingHeader::GetName(nsACString& aName) { 866 aName.AssignLiteral("ReportingHeader"); 867 return NS_OK; 868 } 869 870 void ReportingHeader::MaybeCreateCleanupTimer() { 871 if (mCleanupTimer) { 872 return; 873 } 874 875 if (mOrigins.Count() == 0) { 876 return; 877 } 878 879 uint32_t timeout = StaticPrefs::dom_reporting_cleanup_timeout() * 1000; 880 nsresult rv = 881 NS_NewTimerWithCallback(getter_AddRefs(mCleanupTimer), this, timeout, 882 nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); 883 (void)NS_WARN_IF(NS_FAILED(rv)); 884 } 885 886 void ReportingHeader::MaybeCancelCleanupTimer() { 887 if (!mCleanupTimer) { 888 return; 889 } 890 891 if (mOrigins.Count() != 0) { 892 return; 893 } 894 895 mCleanupTimer->Cancel(); 896 mCleanupTimer = nullptr; 897 } 898 899 NS_INTERFACE_MAP_BEGIN(ReportingHeader) 900 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) 901 NS_INTERFACE_MAP_ENTRY(nsIObserver) 902 NS_INTERFACE_MAP_ENTRY(nsITimerCallback) 903 NS_INTERFACE_MAP_ENTRY(nsINamed) 904 NS_INTERFACE_MAP_END 905 906 NS_IMPL_ADDREF(ReportingHeader) 907 NS_IMPL_RELEASE(ReportingHeader) 908 909 } // namespace mozilla::dom