Bug 1508310 - Implement Report-to header support - part 1 - Header parser, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Sat, 01 Dec 2018 21:26:08 +0100
changeset 505575 c435d7c0173e6b9bf699d80e370a657bdff248ed
parent 505574 d44f09e1a87bda1fbe51a05b8526cad6459ff07b
child 505576 b8e709a8076fa6a5dfd68a045993e2650e124bf6
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1508310
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1508310 - Implement Report-to header support - part 1 - Header parser, r=smaug
dom/locales/en-US/chrome/security/security.properties
dom/reporting/ReportingHeader.cpp
dom/reporting/ReportingHeader.h
dom/reporting/moz.build
dom/webidl/Reporting.webidl
layout/build/nsLayoutStatics.cpp
modules/libpref/init/StaticPrefList.h
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -95,13 +95,22 @@ BlockSubresourceFTP=Loading FTP subresource within http(s) page not allowed (Blocked loading of: “%1$S”)
 # LOCALIZATION NOTE (BrowserUpgradeInsecureDisplayRequest):
 # %1$S is the browser name "brandShortName"; %2$S is the URL of the upgraded request; %1$S is the upgraded scheme.
 BrowserUpgradeInsecureDisplayRequest = %1$S is upgrading an insecure display request ‘%2$S’ to use ‘%3$S’
 # LOCALIZATION NOTE (RunningClearSiteDataValue):
 # %S is the URI of the resource whose data was cleaned up
 RunningClearSiteDataValue=Clear-Site-Data header forced the clean up of “%S” data.
 UnknownClearSiteDataValue=Clear-Site-Data header found. Unknown value “%S”.
 
+# Reporting API
+ReportingHeaderInvalidJSON=Reporting Header: invalid JSON value received.
+ReportingHeaderInvalidNameItem=Reporting Header: invalid name for group.
+ReportingHeaderDuplicateGroup=Reporting Header: ignoring duplicated group named “%S”.
+ReportingHeaderInvalidItem=Reporting Header: ignoring invalid item named “%S”.
+ReportingHeaderInvalidEndpoint=Reporting Header: ignoring invalid endpoint for item named “%S”.
+# LOCALIZATION NOTE(ReportingHeaderInvalidURLEndpoint): %1$S is the invalid URL, %2$S is the group name
+ReportingHeaderInvalidURLEndpoint=Reporting Header: ignoring invalid endpoint URL “%1$S” for item named “%2$S”.
+
 FeaturePolicyUnsupportedFeatureName=Feature Policy: Skipping unsupported feature name “%S”.
 # TODO: would be nice to add a link to the Feature-Policy MDN documentation here. See bug 1449501
 FeaturePolicyInvalidEmptyAllowValue= Feature Policy: Skipping empty allow list for feature: “%S”.
 # TODO: would be nice to add a link to the Feature-Policy MDN documentation here. See bug 1449501
 FeaturePolicyInvalidAllowValue=Feature Policy: Skipping unsupported allow value “%S”.
new file mode 100644
--- /dev/null
+++ b/dom/reporting/ReportingHeader.cpp
@@ -0,0 +1,423 @@
+/* -*- 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 "js/JSON.h"
+#include "mozilla/dom/ReportingBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs.h"
+#include "mozilla/StaticPtr.h"
+#include "nsContentUtils.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsNetUtil.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace 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);
+  gReporting = service;
+}
+
+/* static */ void ReportingHeader::Shutdown() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gReporting) {
+    return;
+  }
+
+  RefPtr<ReportingHeader> service = gReporting;
+  gReporting = 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);
+}
+
+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;
+  }
+
+  MOZ_ASSERT(!strcmp(aTopic, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC));
+
+  // Pref disabled.
+  if (!StaticPrefs::dom_reporting_header_enabled()) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aSubject);
+  if (NS_WARN_IF(!channel)) {
+    return NS_OK;
+  }
+
+  ReportingFromChannel(channel);
+  return NS_OK;
+}
+
+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;
+  }
+
+  nsAutoCString headerValue;
+  rv =
+      aChannel->GetResponseHeader(NS_LITERAL_CSTRING("Report-To"), headerValue);
+  if (NS_FAILED(rv)) {
+    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;
+  }
+
+  UniquePtr<Client> client = ParseHeader(aChannel, uri, headerValue);
+  if (client) {
+    // Here we override the previous data.
+    mOrigins.Put(origin, client.release());
+  }
+}
+
+/* static */ UniquePtr<ReportingHeader::Client> ReportingHeader::ParseHeader(
+    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, PromiseFlatString(json).get(), 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;
+    }
+
+    bool found = false;
+    for (const Group& group : client->mGroups) {
+      if (group.mName == groupName) {
+        found = true;
+        break;
+      }
+    }
+
+    if (found) {
+      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;
+      }
+
+      ReportingEndpoint endpoint;
+      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;
+}
+
+bool ReportingHeader::IsSecureURI(nsIURI* aURI) const {
+  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, NS_LITERAL_CSTRING("Reporting"),
+      windowID, aURI);
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+NS_INTERFACE_MAP_BEGIN(ReportingHeader)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(ReportingHeader)
+NS_IMPL_RELEASE(ReportingHeader)
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/reporting/ReportingHeader.h
@@ -0,0 +1,95 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_ReportingHeader_h
+#define mozilla_dom_ReportingHeader_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsClassHashtable.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+
+class nsIHttpChannel;
+class nsIPrincipal;
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+
+class ReportingHeader final : public nsIObserver {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static void Initialize();
+
+  // Exposed structs for gtests
+
+  struct Endpoint {
+    nsCOMPtr<nsIURI> mUrl;
+    uint32_t mPriority;
+    uint32_t mWeight;
+  };
+
+  struct Group {
+    nsString mName;
+    bool mIncludeSubdomains;
+    int32_t mTTL;
+    TimeStamp mCreationTime;
+    nsTArray<Endpoint> mEndpoints;
+  };
+
+  struct Client {
+    nsTArray<Group> mGroups;
+  };
+
+  static UniquePtr<Client> ParseHeader(nsIHttpChannel* aChannel, nsIURI* aURI,
+                                       const nsACString& aHeaderValue);
+
+ private:
+  ReportingHeader();
+  ~ReportingHeader();
+
+  static void Shutdown();
+
+  // Checks if a channel contains a Report-To header and parses its value.
+  void ReportingFromChannel(nsIHttpChannel* aChannel);
+
+  // This method checks if the protocol handler of the URI has the
+  // URI_IS_POTENTIALLY_TRUSTWORTHY flag.
+  bool IsSecureURI(nsIURI* aURI) const;
+
+  static void LogToConsoleInvalidJSON(nsIHttpChannel* aChannel, nsIURI* aURI);
+
+  static void LogToConsoleDuplicateGroup(nsIHttpChannel* aChannel, nsIURI* aURI,
+                                         const nsAString& aName);
+
+  static void LogToConsoleInvalidNameItem(nsIHttpChannel* aChannel,
+                                          nsIURI* aURI);
+
+  static void LogToConsoleIncompleteItem(nsIHttpChannel* aChannel, nsIURI* aURI,
+                                         const nsAString& aName);
+
+  static void LogToConsoleIncompleteEndpoint(nsIHttpChannel* aChannel,
+                                             nsIURI* aURI,
+                                             const nsAString& aName);
+
+  static void LogToConsoleInvalidURLEndpoint(nsIHttpChannel* aChannel,
+                                             nsIURI* aURI,
+                                             const nsAString& aName,
+                                             const nsAString& aURL);
+
+  static void LogToConsoleInternal(nsIHttpChannel* aChannel, nsIURI* aURI,
+                                   const char* aMsg,
+                                   const nsTArray<nsString>& aParams);
+
+  nsClassHashtable<nsCStringHashKey, Client> mOrigins;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_ReportingHeader_h
--- a/dom/reporting/moz.build
+++ b/dom/reporting/moz.build
@@ -4,26 +4,28 @@
 # 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/.
 
 EXPORTS.mozilla.dom = [
     'DeprecationReportBody.h',
     'FeaturePolicyViolationReportBody.h',
     'Report.h',
     'ReportBody.h',
+    'ReportingHeader.h',
     'ReportingObserver.h',
     'ReportingUtils.h',
     'TestingDeprecatedInterface.h',
 ]
 
 UNIFIED_SOURCES += [
     'DeprecationReportBody.cpp',
     'FeaturePolicyViolationReportBody.cpp',
     'Report.cpp',
     'ReportBody.cpp',
+    'ReportingHeader.cpp',
     'ReportingObserver.cpp',
     'ReportingUtils.cpp',
     'TestingDeprecatedInterface.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 with Files('**'):
--- a/dom/webidl/Reporting.webidl
+++ b/dom/webidl/Reporting.webidl
@@ -50,8 +50,34 @@ interface DeprecationReportBody : Report
  Exposed=(Window,DedicatedWorker)]
 interface TestingDeprecatedInterface {
   [Deprecated="DeprecatedTestingMethod"]
   void deprecatedMethod();
 
   [Deprecated="DeprecatedTestingAttribute"]
   readonly attribute boolean deprecatedAttribute;
 };
+
+// Used internally to process the JSON
+dictionary ReportingHeaderValue {
+  sequence<ReportingItem> items;
+};
+
+// Used internally to process the JSON
+dictionary ReportingItem {
+  // This is a long.
+  any max_age;
+  // This is a sequence of ReportingEndpoint.
+  any endpoints;
+  // This is a string. If missing, the value is 'default'.
+  any group;
+  boolean include_subdomains = false;
+};
+
+// Used internally to process the JSON
+dictionary ReportingEndpoint {
+  // This is a string.
+  any url;
+  // This is an unsigned long.
+  any priority;
+  // This is an unsigned long.
+  any weight;
+};
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -108,16 +108,17 @@
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPresData.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/dom/ipc/IPCBlobInputStreamStorage.h"
 #include "mozilla/dom/U2FTokenManager.h"
 #include "mozilla/dom/PointerEventHandler.h"
 #include "mozilla/dom/RemoteWorkerService.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/ReportingHeader.h"
 #include "nsThreadManager.h"
 #include "mozilla/css/ImageLoader.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 
@@ -281,16 +282,19 @@ nsresult nsLayoutStatics::Initialize() {
   }
 
   nsThreadManager::InitializeShutdownObserver();
 
   mozilla::Fuzzyfox::Start();
 
   ClearSiteData::Initialize();
 
+  // Reporting API.
+  ReportingHeader::Initialize();
+
   return NS_OK;
 }
 
 void nsLayoutStatics::Shutdown() {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
 
   if (XRE_IsParentProcess() || XRE_IsContentProcess()) {
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1903,13 +1903,19 @@ VARCACHE_PREF(
 #endif
 VARCACHE_PREF(
   "dom.reporting.featurePolicy.enabled",
    dom_reporting_featurePolicy_enabled,
   RelaxedAtomicBool, PREF_VALUE
 )
 #undef PREF_VALUE
 
+VARCACHE_PREF(
+  "dom.reporting.header.enabled",
+   dom_reporting_header_enabled,
+  RelaxedAtomicBool, false
+)
+
 //---------------------------------------------------------------------------
 // End of prefs
 //---------------------------------------------------------------------------
 
 // clang-format on