mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 08:18:54 +02:00 
			
		
		
		
	 198c331213
			
		
	
	
		198c331213
		
	
	
	
	
		
			
			Use it liberally across the tree. This could be cleaned up even more in the future. Differential Revision: https://phabricator.services.mozilla.com/D218114
		
			
				
	
	
		
			889 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			889 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/dom/ReportingHeader.h"
 | |
| #include <limits>
 | |
| 
 | |
| #include "js/Array.h"  // JS::GetArrayLength, JS::IsArrayObject
 | |
| #include "js/JSON.h"
 | |
| #include "js/PropertyAndElement.h"  // JS_GetElement
 | |
| #include "mozilla/dom/ReportingBinding.h"
 | |
| #include "mozilla/dom/ScriptSettings.h"
 | |
| #include "mozilla/dom/SimpleGlobalObject.h"
 | |
| #include "mozilla/ipc/BackgroundUtils.h"
 | |
| #include "mozilla/net/SFVService.h"
 | |
| #include "mozilla/OriginAttributes.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "mozilla/StaticPrefs_dom.h"
 | |
| #include "mozilla/StaticPtr.h"
 | |
| #include "nsCOMPtr.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsIEffectiveTLDService.h"
 | |
| #include "nsIHttpChannel.h"
 | |
| #include "nsIHttpProtocolHandler.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsIPrincipal.h"
 | |
| #include "nsIRandomGenerator.h"
 | |
| #include "nsIScriptError.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "nsXULAppAPI.h"
 | |
| 
 | |
| #define REPORTING_PURGE_ALL "reporting:purge-all"
 | |
| #define REPORTING_PURGE_HOST "reporting:purge-host"
 | |
| 
 | |
| namespace mozilla::dom {
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| StaticRefPtr<ReportingHeader> gReporting;
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::Initialize() {
 | |
|   MOZ_ASSERT(!gReporting);
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (!XRE_IsParentProcess()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<ReportingHeader> service = new ReportingHeader();
 | |
| 
 | |
|   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
 | |
|   if (NS_WARN_IF(!obs)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   obs->AddObserver(service, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC, false);
 | |
|   obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
 | |
|   obs->AddObserver(service, "clear-origin-attributes-data", false);
 | |
|   obs->AddObserver(service, REPORTING_PURGE_HOST, false);
 | |
|   obs->AddObserver(service, REPORTING_PURGE_ALL, false);
 | |
| 
 | |
|   gReporting = service;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::Shutdown() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (!gReporting) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<ReportingHeader> service = gReporting;
 | |
|   gReporting = nullptr;
 | |
| 
 | |
|   if (service->mCleanupTimer) {
 | |
|     service->mCleanupTimer->Cancel();
 | |
|     service->mCleanupTimer = nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
 | |
|   if (NS_WARN_IF(!obs)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   obs->RemoveObserver(service, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC);
 | |
|   obs->RemoveObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 | |
|   obs->RemoveObserver(service, "clear-origin-attributes-data");
 | |
|   obs->RemoveObserver(service, REPORTING_PURGE_HOST);
 | |
|   obs->RemoveObserver(service, REPORTING_PURGE_ALL);
 | |
| }
 | |
| 
 | |
| ReportingHeader::ReportingHeader() = default;
 | |
| ReportingHeader::~ReportingHeader() = default;
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ReportingHeader::Observe(nsISupports* aSubject, const char* aTopic,
 | |
|                          const char16_t* aData) {
 | |
|   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
 | |
|     Shutdown();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Pref disabled.
 | |
|   if (!StaticPrefs::dom_reporting_header_enabled()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (!strcmp(aTopic, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC)) {
 | |
|     nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aSubject);
 | |
|     if (NS_WARN_IF(!channel)) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     ReportingFromChannel(channel);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (!strcmp(aTopic, REPORTING_PURGE_HOST)) {
 | |
|     RemoveOriginsFromHost(nsDependentString(aData));
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (!strcmp(aTopic, "clear-origin-attributes-data")) {
 | |
|     OriginAttributesPattern pattern;
 | |
|     if (!pattern.Init(nsDependentString(aData))) {
 | |
|       NS_ERROR("Cannot parse origin attributes pattern");
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     RemoveOriginsFromOriginAttributesPattern(pattern);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (!strcmp(aTopic, REPORTING_PURGE_ALL)) {
 | |
|     RemoveOrigins();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| void ReportingHeader::ReportingFromChannel(nsIHttpChannel* aChannel) {
 | |
|   MOZ_ASSERT(aChannel);
 | |
| 
 | |
|   if (!StaticPrefs::dom_reporting_header_enabled()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We want to use the final URI to check if Report-To should be allowed or
 | |
|   // not.
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!IsSecureURI(uri)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (NS_UsePrivateBrowsing(aChannel)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
 | |
|   if (NS_WARN_IF(!ssm)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIPrincipal> principal;
 | |
|   rv = ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(principal));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv)) || !principal) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString origin;
 | |
|   rv = principal->GetOrigin(origin);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Parse Report-To and Reporting-Endpoints headers
 | |
|   UniquePtr<Client> client;
 | |
|   nsAutoCString header;
 | |
| 
 | |
|   if (NS_SUCCEEDED(
 | |
|           aChannel->GetResponseHeader("Reporting-Endpoints"_ns, header))) {
 | |
|     client = ParseReportingEndpointsHeader(header, uri);
 | |
|   } else if (NS_SUCCEEDED(
 | |
|                  aChannel->GetResponseHeader("Report-To"_ns, header))) {
 | |
|     client = ParseReportToHeader(aChannel, uri, header);
 | |
|   }
 | |
| 
 | |
|   if (!client) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Here we override the previous data.
 | |
|   mOrigins.InsertOrUpdate(origin, std::move(client));
 | |
| 
 | |
|   MaybeCreateCleanupTimer();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| UniquePtr<ReportingHeader::Client>
 | |
| ReportingHeader::ParseReportingEndpointsHeader(const nsACString& aHeaderValue,
 | |
|                                                nsIURI* aURI) {
 | |
|   nsCOMPtr<nsISFVService> sfv = mozilla::net::GetSFVService();
 | |
| 
 | |
|   nsAutoCString uriSpec;
 | |
|   aURI->GetSpec(uriSpec);
 | |
| 
 | |
|   nsCOMPtr<nsIURI> baseURL;
 | |
|   if (NS_FAILED(NS_NewURI(getter_AddRefs(baseURL), uriSpec))) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsISFVDictionary> parsedHeader;
 | |
|   if (NS_FAILED(
 | |
|           sfv->ParseDictionary(aHeaderValue, getter_AddRefs(parsedHeader)))) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsTArray<nsCString> keys;
 | |
|   if (NS_FAILED(parsedHeader->Keys(keys))) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   UniquePtr<Client> client = MakeUnique<Client>();
 | |
| 
 | |
|   for (const auto& key : keys) {
 | |
|     // Extract an SFV data object from each dictionary entry
 | |
|     nsCOMPtr<nsISFVItemOrInnerList> iil;
 | |
|     if (NS_FAILED(parsedHeader->Get(key, getter_AddRefs(iil)))) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // An item needs to be extracted from the ItemOrInnerList member-value
 | |
|     nsCOMPtr<nsISFVBareItem> value;
 | |
|     if (nsCOMPtr<nsISFVInnerList> innerList = do_QueryInterface(iil)) {
 | |
|       // Extract the first entry of each inner list, which should contain the
 | |
|       // endpoint's URL string
 | |
|       nsTArray<RefPtr<nsISFVItem>> items;
 | |
| 
 | |
|       if (NS_FAILED(innerList->GetItems(items))) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       if (items.IsEmpty()) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsISFVItem> firstItem(items[0]);
 | |
| 
 | |
|       if (NS_FAILED(firstItem->GetValue(getter_AddRefs(value)))) {
 | |
|         continue;
 | |
|       }
 | |
|     } else if (nsCOMPtr<nsISFVItem> listItem = do_QueryInterface(iil)) {
 | |
|       if (NS_FAILED(listItem->GetValue(getter_AddRefs(value)))) {
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Ensure that the item's data type is a string, so the URL can be properly
 | |
|     // parsed
 | |
|     nsCOMPtr<nsISFVString> sfvString(do_QueryInterface(value));
 | |
|     if (!sfvString) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsAutoCString endpointURLString;
 | |
|     if (NS_FAILED(sfvString->GetValue(endpointURLString))) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // Convert the URL string into a URI
 | |
|     nsCOMPtr<nsIURI> endpointURL;
 | |
|     nsresult rv = NS_NewURI(getter_AddRefs(endpointURL),
 | |
|                             endpointURLString.get(), baseURL);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!IsSecureURI(endpointURL)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     Group* group = client->mGroups.AppendElement();
 | |
|     group->mCreationTime = TimeStamp::Now();
 | |
|     group->mTTL = std::numeric_limits<int32_t>::max();
 | |
|     group->mName = NS_ConvertUTF8toUTF16(key);
 | |
| 
 | |
|     // Use data extracted from dictionary entry to create an endpoint
 | |
|     Endpoint* ep = group->mEndpoints.AppendElement();
 | |
|     ep->mUrl = endpointURL;
 | |
|     ep->mEndpointName = key;
 | |
|     ep->mFailures = 0;
 | |
|     ep->mPriority = 1;
 | |
|     ep->mWeight = 1;
 | |
|   }
 | |
| 
 | |
|   if (client->mGroups.IsEmpty()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return client;
 | |
| }
 | |
| 
 | |
| /* static */ UniquePtr<ReportingHeader::Client>
 | |
| ReportingHeader::ParseReportToHeader(nsIHttpChannel* aChannel, nsIURI* aURI,
 | |
|                                      const nsACString& aHeaderValue) {
 | |
|   MOZ_ASSERT(aURI);
 | |
|   // aChannel can be null in gtest
 | |
| 
 | |
|   AutoJSAPI jsapi;
 | |
| 
 | |
|   JSObject* cleanGlobal =
 | |
|       SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::BindingDetail);
 | |
|   if (NS_WARN_IF(!cleanGlobal)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(!jsapi.Init(cleanGlobal))) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // WebIDL dictionary parses single items. Let's create a object to parse the
 | |
|   // header.
 | |
|   nsAutoString json;
 | |
|   json.AppendASCII("{ \"items\": [");
 | |
|   json.Append(NS_ConvertUTF8toUTF16(aHeaderValue));
 | |
|   json.AppendASCII("]}");
 | |
| 
 | |
|   JSContext* cx = jsapi.cx();
 | |
|   JS::Rooted<JS::Value> jsonValue(cx);
 | |
|   bool ok = JS_ParseJSON(cx, json.BeginReading(), json.Length(), &jsonValue);
 | |
|   if (!ok) {
 | |
|     LogToConsoleInvalidJSON(aChannel, aURI);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   dom::ReportingHeaderValue data;
 | |
|   if (!data.Init(cx, jsonValue)) {
 | |
|     LogToConsoleInvalidJSON(aChannel, aURI);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (!data.mItems.WasPassed() || data.mItems.Value().IsEmpty()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   UniquePtr<Client> client = MakeUnique<Client>();
 | |
| 
 | |
|   for (const dom::ReportingItem& item : data.mItems.Value()) {
 | |
|     nsAutoString groupName;
 | |
| 
 | |
|     if (item.mGroup.isUndefined()) {
 | |
|       groupName.AssignLiteral("default");
 | |
|     } else if (!item.mGroup.isString()) {
 | |
|       LogToConsoleInvalidNameItem(aChannel, aURI);
 | |
|       continue;
 | |
|     } else {
 | |
|       JS::Rooted<JSString*> groupStr(cx, item.mGroup.toString());
 | |
|       MOZ_ASSERT(groupStr);
 | |
| 
 | |
|       nsAutoJSString string;
 | |
|       if (NS_WARN_IF(!string.init(cx, groupStr))) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       groupName = string;
 | |
|     }
 | |
| 
 | |
|     if (!item.mMax_age.isNumber() || !item.mEndpoints.isObject()) {
 | |
|       LogToConsoleIncompleteItem(aChannel, aURI, groupName);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     JS::Rooted<JSObject*> endpoints(cx, &item.mEndpoints.toObject());
 | |
|     MOZ_ASSERT(endpoints);
 | |
| 
 | |
|     bool isArray = false;
 | |
|     if (!JS::IsArrayObject(cx, endpoints, &isArray) || !isArray) {
 | |
|       LogToConsoleIncompleteItem(aChannel, aURI, groupName);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     uint32_t endpointsLength;
 | |
|     if (!JS::GetArrayLength(cx, endpoints, &endpointsLength) ||
 | |
|         endpointsLength == 0) {
 | |
|       LogToConsoleIncompleteItem(aChannel, aURI, groupName);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     const auto [begin, end] = client->mGroups.NonObservingRange();
 | |
|     if (std::any_of(begin, end, [&groupName](const Group& group) {
 | |
|           return group.mName == groupName;
 | |
|         })) {
 | |
|       LogToConsoleDuplicateGroup(aChannel, aURI, groupName);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     Group* group = client->mGroups.AppendElement();
 | |
|     group->mName = groupName;
 | |
|     group->mIncludeSubdomains = item.mInclude_subdomains;
 | |
|     group->mTTL = item.mMax_age.toNumber();
 | |
|     group->mCreationTime = TimeStamp::Now();
 | |
| 
 | |
|     for (uint32_t i = 0; i < endpointsLength; ++i) {
 | |
|       JS::Rooted<JS::Value> element(cx);
 | |
|       if (!JS_GetElement(cx, endpoints, i, &element)) {
 | |
|         return nullptr;
 | |
|       }
 | |
| 
 | |
|       RootedDictionary<ReportingEndpoint> endpoint(cx);
 | |
|       if (!endpoint.Init(cx, element)) {
 | |
|         LogToConsoleIncompleteEndpoint(aChannel, aURI, groupName);
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       if (!endpoint.mUrl.isString() ||
 | |
|           (!endpoint.mPriority.isUndefined() &&
 | |
|            (!endpoint.mPriority.isNumber() ||
 | |
|             endpoint.mPriority.toNumber() < 0)) ||
 | |
|           (!endpoint.mWeight.isUndefined() &&
 | |
|            (!endpoint.mWeight.isNumber() || endpoint.mWeight.toNumber() < 0))) {
 | |
|         LogToConsoleIncompleteEndpoint(aChannel, aURI, groupName);
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       JS::Rooted<JSString*> endpointUrl(cx, endpoint.mUrl.toString());
 | |
|       MOZ_ASSERT(endpointUrl);
 | |
| 
 | |
|       nsAutoJSString endpointString;
 | |
|       if (NS_WARN_IF(!endpointString.init(cx, endpointUrl))) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsIURI> uri;
 | |
|       nsresult rv = NS_NewURI(getter_AddRefs(uri), endpointString);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         LogToConsoleInvalidURLEndpoint(aChannel, aURI, groupName,
 | |
|                                        endpointString);
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       Endpoint* ep = group->mEndpoints.AppendElement();
 | |
|       ep->mUrl = uri;
 | |
|       ep->mPriority =
 | |
|           endpoint.mPriority.isUndefined() ? 1 : endpoint.mPriority.toNumber();
 | |
|       ep->mWeight =
 | |
|           endpoint.mWeight.isUndefined() ? 1 : endpoint.mWeight.toNumber();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (client->mGroups.IsEmpty()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return client;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool ReportingHeader::IsSecureURI(nsIURI* aURI) {
 | |
|   MOZ_ASSERT(aURI);
 | |
| 
 | |
|   bool prioriAuthenticated = false;
 | |
|   if (NS_WARN_IF(NS_FAILED(NS_URIChainHasFlags(
 | |
|           aURI, nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY,
 | |
|           &prioriAuthenticated)))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return prioriAuthenticated;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::LogToConsoleInvalidJSON(nsIHttpChannel* aChannel,
 | |
|                                               nsIURI* aURI) {
 | |
|   nsTArray<nsString> params;
 | |
|   LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidJSON", params);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::LogToConsoleDuplicateGroup(nsIHttpChannel* aChannel,
 | |
|                                                  nsIURI* aURI,
 | |
|                                                  const nsAString& aName) {
 | |
|   nsTArray<nsString> params;
 | |
|   params.AppendElement(aName);
 | |
| 
 | |
|   LogToConsoleInternal(aChannel, aURI, "ReportingHeaderDuplicateGroup", params);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::LogToConsoleInvalidNameItem(nsIHttpChannel* aChannel,
 | |
|                                                   nsIURI* aURI) {
 | |
|   nsTArray<nsString> params;
 | |
|   LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidNameItem",
 | |
|                        params);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::LogToConsoleIncompleteItem(nsIHttpChannel* aChannel,
 | |
|                                                  nsIURI* aURI,
 | |
|                                                  const nsAString& aName) {
 | |
|   nsTArray<nsString> params;
 | |
|   params.AppendElement(aName);
 | |
| 
 | |
|   LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidItem", params);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::LogToConsoleIncompleteEndpoint(nsIHttpChannel* aChannel,
 | |
|                                                      nsIURI* aURI,
 | |
|                                                      const nsAString& aName) {
 | |
|   nsTArray<nsString> params;
 | |
|   params.AppendElement(aName);
 | |
| 
 | |
|   LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidEndpoint",
 | |
|                        params);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::LogToConsoleInvalidURLEndpoint(nsIHttpChannel* aChannel,
 | |
|                                                      nsIURI* aURI,
 | |
|                                                      const nsAString& aName,
 | |
|                                                      const nsAString& aURL) {
 | |
|   nsTArray<nsString> params;
 | |
|   params.AppendElement(aURL);
 | |
|   params.AppendElement(aName);
 | |
| 
 | |
|   LogToConsoleInternal(aChannel, aURI, "ReportingHeaderInvalidURLEndpoint",
 | |
|                        params);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::LogToConsoleInternal(nsIHttpChannel* aChannel,
 | |
|                                            nsIURI* aURI, const char* aMsg,
 | |
|                                            const nsTArray<nsString>& aParams) {
 | |
|   MOZ_ASSERT(aURI);
 | |
| 
 | |
|   if (!aChannel) {
 | |
|     // We are in a gtest.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   uint64_t windowID = 0;
 | |
| 
 | |
|   nsresult rv = aChannel->GetTopLevelContentWindowId(&windowID);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!windowID) {
 | |
|     nsCOMPtr<nsILoadGroup> loadGroup;
 | |
|     nsresult rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (loadGroup) {
 | |
|       windowID = nsContentUtils::GetInnerWindowID(loadGroup);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsAutoString localizedMsg;
 | |
|   rv = nsContentUtils::FormatLocalizedString(
 | |
|       nsContentUtils::eSECURITY_PROPERTIES, aMsg, aParams, localizedMsg);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   rv = nsContentUtils::ReportToConsoleByWindowID(
 | |
|       localizedMsg, nsIScriptError::infoFlag, "Reporting"_ns, windowID,
 | |
|       SourceLocation(aURI));
 | |
|   Unused << NS_WARN_IF(NS_FAILED(rv));
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::GetEndpointForReport(
 | |
|     const nsAString& aGroupName,
 | |
|     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
 | |
|     nsACString& aEndpointURI) {
 | |
|   auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo);
 | |
|   if (NS_WARN_IF(principalOrErr.isErr())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
 | |
|   GetEndpointForReport(aGroupName, principal, aEndpointURI);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::GetEndpointForReport(const nsAString& aGroupName,
 | |
|                                            nsIPrincipal* aPrincipal,
 | |
|                                            nsACString& aEndpointURI) {
 | |
|   MOZ_ASSERT(aEndpointURI.IsEmpty());
 | |
| 
 | |
|   if (!gReporting) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString origin;
 | |
|   nsresult rv = aPrincipal->GetOrigin(origin);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Client* client = gReporting->mOrigins.Get(origin);
 | |
|   if (!client) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const auto [begin, end] = client->mGroups.NonObservingRange();
 | |
|   const auto foundIt = std::find_if(
 | |
|       begin, end,
 | |
|       [&aGroupName](const Group& group) { return group.mName == aGroupName; });
 | |
|   if (foundIt != end) {
 | |
|     GetEndpointForReportInternal(*foundIt, aEndpointURI);
 | |
|   }
 | |
| 
 | |
|   // XXX More explicitly report an error if not found?
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::GetEndpointForReportInternal(
 | |
|     const ReportingHeader::Group& aGroup, nsACString& aEndpointURI) {
 | |
|   TimeDuration diff = TimeStamp::Now() - aGroup.mCreationTime;
 | |
|   if (diff.ToSeconds() > aGroup.mTTL) {
 | |
|     // Expired.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aGroup.mEndpoints.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   int64_t minPriority = -1;
 | |
|   uint32_t totalWeight = 0;
 | |
| 
 | |
|   for (const Endpoint& endpoint : aGroup.mEndpoints.NonObservingRange()) {
 | |
|     if (minPriority == -1 || minPriority > endpoint.mPriority) {
 | |
|       minPriority = endpoint.mPriority;
 | |
|       totalWeight = endpoint.mWeight;
 | |
|     } else if (minPriority == endpoint.mPriority) {
 | |
|       totalWeight += endpoint.mWeight;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIRandomGenerator> randomGenerator =
 | |
|       do_GetService("@mozilla.org/security/random-generator;1");
 | |
|   if (NS_WARN_IF(!randomGenerator)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   uint32_t randomNumber = 0;
 | |
| 
 | |
|   nsresult rv = randomGenerator->GenerateRandomBytesInto(randomNumber);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   totalWeight = randomNumber % totalWeight;
 | |
| 
 | |
|   const auto [begin, end] = aGroup.mEndpoints.NonObservingRange();
 | |
|   const auto foundIt = std::find_if(
 | |
|       begin, end, [minPriority, totalWeight](const Endpoint& endpoint) {
 | |
|         return minPriority == endpoint.mPriority &&
 | |
|                totalWeight < endpoint.mWeight;
 | |
|       });
 | |
|   if (foundIt != end) {
 | |
|     Unused << NS_WARN_IF(NS_FAILED(foundIt->mUrl->GetSpec(aEndpointURI)));
 | |
|   }
 | |
|   // XXX More explicitly report an error if not found?
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ReportingHeader::RemoveEndpoint(
 | |
|     const nsAString& aGroupName, const nsACString& aEndpointURL,
 | |
|     const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
 | |
|   if (!gReporting) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = NS_NewURI(getter_AddRefs(uri), aEndpointURL);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo);
 | |
|   if (NS_WARN_IF(principalOrErr.isErr())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString origin;
 | |
|   rv = principalOrErr.unwrap()->GetOrigin(origin);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Client* client = gReporting->mOrigins.Get(origin);
 | |
|   if (!client) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Scope for the group iterator.
 | |
|   {
 | |
|     nsTObserverArray<Group>::BackwardIterator iter(client->mGroups);
 | |
|     while (iter.HasMore()) {
 | |
|       const Group& group = iter.GetNext();
 | |
|       if (group.mName != aGroupName) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // Scope for the endpoint iterator.
 | |
|       {
 | |
|         nsTObserverArray<Endpoint>::BackwardIterator endpointIter(
 | |
|             group.mEndpoints);
 | |
|         while (endpointIter.HasMore()) {
 | |
|           const Endpoint& endpoint = endpointIter.GetNext();
 | |
| 
 | |
|           bool equal = false;
 | |
|           rv = endpoint.mUrl->Equals(uri, &equal);
 | |
|           if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           if (equal) {
 | |
|             endpointIter.Remove();
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (group.mEndpoints.IsEmpty()) {
 | |
|         iter.Remove();
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (client->mGroups.IsEmpty()) {
 | |
|     gReporting->mOrigins.Remove(origin);
 | |
|     gReporting->MaybeCancelCleanupTimer();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ReportingHeader::RemoveOriginsFromHost(const nsAString& aHost) {
 | |
|   nsCOMPtr<nsIEffectiveTLDService> tldService =
 | |
|       do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
 | |
|   if (NS_WARN_IF(!tldService)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   NS_ConvertUTF16toUTF8 host(aHost);
 | |
| 
 | |
|   for (auto iter = mOrigins.Iter(); !iter.Done(); iter.Next()) {
 | |
|     bool hasRootDomain = false;
 | |
|     nsresult rv = tldService->HasRootDomain(iter.Key(), host, &hasRootDomain);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv)) || !hasRootDomain) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     iter.Remove();
 | |
|   }
 | |
| 
 | |
|   MaybeCancelCleanupTimer();
 | |
| }
 | |
| 
 | |
| void ReportingHeader::RemoveOriginsFromOriginAttributesPattern(
 | |
|     const OriginAttributesPattern& aPattern) {
 | |
|   for (auto iter = mOrigins.Iter(); !iter.Done(); iter.Next()) {
 | |
|     nsAutoCString suffix;
 | |
|     OriginAttributes attr;
 | |
|     if (NS_WARN_IF(!attr.PopulateFromOrigin(iter.Key(), suffix))) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (aPattern.Matches(attr)) {
 | |
|       iter.Remove();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   MaybeCancelCleanupTimer();
 | |
| }
 | |
| 
 | |
| void ReportingHeader::RemoveOrigins() {
 | |
|   mOrigins.Clear();
 | |
|   MaybeCancelCleanupTimer();
 | |
| }
 | |
| 
 | |
| void ReportingHeader::RemoveOriginsForTTL() {
 | |
|   TimeStamp now = TimeStamp::Now();
 | |
| 
 | |
|   for (auto iter = mOrigins.Iter(); !iter.Done(); iter.Next()) {
 | |
|     Client* client = iter.UserData();
 | |
| 
 | |
|     // Scope of the iterator.
 | |
|     {
 | |
|       nsTObserverArray<Group>::BackwardIterator groupIter(client->mGroups);
 | |
|       while (groupIter.HasMore()) {
 | |
|         const Group& group = groupIter.GetNext();
 | |
|         TimeDuration diff = now - group.mCreationTime;
 | |
|         if (diff.ToSeconds() > group.mTTL) {
 | |
|           groupIter.Remove();
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (client->mGroups.IsEmpty()) {
 | |
|       iter.Remove();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool ReportingHeader::HasReportingHeaderForOrigin(const nsACString& aOrigin) {
 | |
|   if (!gReporting) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return gReporting->mOrigins.Contains(aOrigin);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ReportingHeader::Notify(nsITimer* aTimer) {
 | |
|   mCleanupTimer = nullptr;
 | |
| 
 | |
|   RemoveOriginsForTTL();
 | |
|   MaybeCreateCleanupTimer();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ReportingHeader::GetName(nsACString& aName) {
 | |
|   aName.AssignLiteral("ReportingHeader");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void ReportingHeader::MaybeCreateCleanupTimer() {
 | |
|   if (mCleanupTimer) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mOrigins.Count() == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   uint32_t timeout = StaticPrefs::dom_reporting_cleanup_timeout() * 1000;
 | |
|   nsresult rv =
 | |
|       NS_NewTimerWithCallback(getter_AddRefs(mCleanupTimer), this, timeout,
 | |
|                               nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY);
 | |
|   Unused << NS_WARN_IF(NS_FAILED(rv));
 | |
| }
 | |
| 
 | |
| void ReportingHeader::MaybeCancelCleanupTimer() {
 | |
|   if (!mCleanupTimer) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mOrigins.Count() != 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mCleanupTimer->Cancel();
 | |
|   mCleanupTimer = nullptr;
 | |
| }
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN(ReportingHeader)
 | |
|   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsINamed)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_ADDREF(ReportingHeader)
 | |
| NS_IMPL_RELEASE(ReportingHeader)
 | |
| 
 | |
| }  // namespace mozilla::dom
 |