tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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