forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			407 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			407 lines
		
	
	
	
		
			11 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/EndpointForReportChild.h"
 | 
						|
#include "mozilla/dom/Fetch.h"
 | 
						|
#include "mozilla/dom/Navigator.h"
 | 
						|
#include "mozilla/dom/Promise.h"
 | 
						|
#include "mozilla/dom/ReportDeliver.h"
 | 
						|
#include "mozilla/dom/Request.h"
 | 
						|
#include "mozilla/dom/RequestBinding.h"
 | 
						|
#include "mozilla/dom/Response.h"
 | 
						|
#include "mozilla/ipc/BackgroundChild.h"
 | 
						|
#include "mozilla/ipc/PBackgroundChild.h"
 | 
						|
#include "mozilla/ipc/PBackgroundSharedTypes.h"
 | 
						|
#include "nsGlobalWindowInner.h"
 | 
						|
#include "nsIGlobalObject.h"
 | 
						|
#include "nsIXPConnect.h"
 | 
						|
#include "nsNetUtil.h"
 | 
						|
#include "nsStringStream.h"
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
namespace dom {
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
StaticRefPtr<ReportDeliver> gReportDeliver;
 | 
						|
 | 
						|
class ReportFetchHandler final : public PromiseNativeHandler {
 | 
						|
 public:
 | 
						|
  NS_DECL_ISUPPORTS
 | 
						|
 | 
						|
  explicit ReportFetchHandler(
 | 
						|
      const nsTArray<ReportDeliver::ReportData>& aReportData)
 | 
						|
      : mReports(aReportData.Clone()) {}
 | 
						|
 | 
						|
  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
 | 
						|
    if (!gReportDeliver) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (NS_WARN_IF(!aValue.isObject())) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
 | 
						|
    MOZ_ASSERT(obj);
 | 
						|
 | 
						|
    {
 | 
						|
      Response* response = nullptr;
 | 
						|
      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response, &obj, response)))) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (response->Status() == 410) {
 | 
						|
        mozilla::ipc::PBackgroundChild* actorChild =
 | 
						|
            mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
 | 
						|
 | 
						|
        for (const auto& report : mReports) {
 | 
						|
          mozilla::ipc::PrincipalInfo principalInfo;
 | 
						|
          nsresult rv =
 | 
						|
              PrincipalToPrincipalInfo(report.mPrincipal, &principalInfo);
 | 
						|
          if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
            continue;
 | 
						|
          }
 | 
						|
 | 
						|
          actorChild->SendRemoveEndpoint(report.mGroupName, report.mEndpointURL,
 | 
						|
                                         principalInfo);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
 | 
						|
    if (gReportDeliver) {
 | 
						|
      for (auto& report : mReports) {
 | 
						|
        ++report.mFailures;
 | 
						|
        gReportDeliver->AppendReportData(report);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  ~ReportFetchHandler() = default;
 | 
						|
 | 
						|
  nsTArray<ReportDeliver::ReportData> mReports;
 | 
						|
};
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS0(ReportFetchHandler)
 | 
						|
 | 
						|
struct StringWriteFunc final : public JSONWriteFunc {
 | 
						|
  nsACString&
 | 
						|
      mBuffer;  // The lifetime of the struct must be bound to the buffer
 | 
						|
  explicit StringWriteFunc(nsACString& aBuffer) : mBuffer(aBuffer) {}
 | 
						|
 | 
						|
  void Write(const char* aStr) override { mBuffer.Append(aStr); }
 | 
						|
  void Write(const char* aStr, size_t aLen) override {
 | 
						|
    mBuffer.Append(aStr, aLen);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class ReportJSONWriter final : public JSONWriter {
 | 
						|
 public:
 | 
						|
  explicit ReportJSONWriter(nsACString& aOutput)
 | 
						|
      : JSONWriter(MakeUnique<StringWriteFunc>(aOutput)) {}
 | 
						|
 | 
						|
  void JSONProperty(const char* aProperty, const char* aJSON) {
 | 
						|
    Separator();
 | 
						|
    PropertyNameAndColon(aProperty);
 | 
						|
    mWriter->Write(aJSON);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
void SendReports(nsTArray<ReportDeliver::ReportData>& aReports,
 | 
						|
                 const nsCString& aEndPointUrl, nsIPrincipal* aPrincipal) {
 | 
						|
  if (NS_WARN_IF(aReports.IsEmpty())) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  nsIXPConnect* xpc = nsContentUtils::XPConnect();
 | 
						|
  MOZ_ASSERT(xpc, "This should never be null!");
 | 
						|
 | 
						|
  nsCOMPtr<nsIGlobalObject> globalObject;
 | 
						|
  {
 | 
						|
    AutoJSAPI jsapi;
 | 
						|
    jsapi.Init();
 | 
						|
 | 
						|
    JSContext* cx = jsapi.cx();
 | 
						|
    JS::Rooted<JSObject*> sandbox(cx);
 | 
						|
    nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
 | 
						|
    if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
 | 
						|
    // global.
 | 
						|
    MOZ_ASSERT(JS_IsGlobalObject(sandbox));
 | 
						|
 | 
						|
    globalObject = xpc::NativeGlobal(sandbox);
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!globalObject)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // The body
 | 
						|
  nsAutoCString body;
 | 
						|
  ReportJSONWriter w(body);
 | 
						|
 | 
						|
  w.StartArrayElement();
 | 
						|
  for (const auto& report : aReports) {
 | 
						|
    MOZ_ASSERT(report.mPrincipal == aPrincipal);
 | 
						|
    MOZ_ASSERT(report.mEndpointURL == aEndPointUrl);
 | 
						|
    w.StartObjectElement();
 | 
						|
    w.IntProperty("age",
 | 
						|
                  (TimeStamp::Now() - report.mCreationTime).ToMilliseconds());
 | 
						|
    w.StringProperty("type", NS_ConvertUTF16toUTF8(report.mType).get());
 | 
						|
    w.StringProperty("url", NS_ConvertUTF16toUTF8(report.mURL).get());
 | 
						|
    w.StringProperty("user_agent",
 | 
						|
                     NS_ConvertUTF16toUTF8(report.mUserAgent).get());
 | 
						|
    w.JSONProperty("body", report.mReportBodyJSON.get());
 | 
						|
    w.EndObject();
 | 
						|
  }
 | 
						|
  w.EndArray();
 | 
						|
 | 
						|
  // The body as stream
 | 
						|
  nsCOMPtr<nsIInputStream> streamBody;
 | 
						|
  nsresult rv = NS_NewCStringInputStream(getter_AddRefs(streamBody), body);
 | 
						|
 | 
						|
  // Headers
 | 
						|
  IgnoredErrorResult error;
 | 
						|
  RefPtr<InternalHeaders> internalHeaders =
 | 
						|
      new InternalHeaders(HeadersGuardEnum::Request);
 | 
						|
  internalHeaders->Set("Content-Type"_ns, "application/reports+json"_ns, error);
 | 
						|
  if (NS_WARN_IF(error.Failed())) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // URL and fragments
 | 
						|
  nsCOMPtr<nsIURI> uri;
 | 
						|
  rv = NS_NewURI(getter_AddRefs(uri), aEndPointUrl);
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIURI> uriClone;
 | 
						|
  rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriClone));
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  nsAutoCString uriSpec;
 | 
						|
  rv = uriClone->GetSpec(uriSpec);
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  nsAutoCString uriFragment;
 | 
						|
  rv = uri->GetRef(uriFragment);
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  auto internalRequest = MakeSafeRefPtr<InternalRequest>(uriSpec, uriFragment);
 | 
						|
 | 
						|
  internalRequest->SetMethod("POST"_ns);
 | 
						|
  internalRequest->SetBody(streamBody, body.Length());
 | 
						|
  internalRequest->SetHeaders(internalHeaders);
 | 
						|
  internalRequest->SetSkipServiceWorker();
 | 
						|
  // TODO: internalRequest->SetContentPolicyType(TYPE_REPORT);
 | 
						|
  internalRequest->SetMode(RequestMode::Cors);
 | 
						|
  internalRequest->SetCredentialsMode(RequestCredentials::Include);
 | 
						|
 | 
						|
  RefPtr<Request> request =
 | 
						|
      new Request(globalObject, std::move(internalRequest), nullptr);
 | 
						|
 | 
						|
  RequestOrUSVString fetchInput;
 | 
						|
  fetchInput.SetAsRequest() = request;
 | 
						|
 | 
						|
  RefPtr<Promise> promise = FetchRequest(
 | 
						|
      globalObject, fetchInput, RequestInit(), CallerType::NonSystem, error);
 | 
						|
  if (error.Failed()) {
 | 
						|
    for (auto& report : aReports) {
 | 
						|
      ++report.mFailures;
 | 
						|
      if (gReportDeliver) {
 | 
						|
        gReportDeliver->AppendReportData(report);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<ReportFetchHandler> handler = new ReportFetchHandler(aReports);
 | 
						|
  promise->AppendNativeHandler(handler);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace
 | 
						|
 | 
						|
/* static */
 | 
						|
void ReportDeliver::Record(nsPIDOMWindowInner* aWindow, const nsAString& aType,
 | 
						|
                           const nsAString& aGroupName, const nsAString& aURL,
 | 
						|
                           ReportBody* aBody) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(aWindow);
 | 
						|
  MOZ_ASSERT(aBody);
 | 
						|
 | 
						|
  nsAutoCString reportBodyJSON;
 | 
						|
  ReportJSONWriter w(reportBodyJSON);
 | 
						|
 | 
						|
  w.Start();
 | 
						|
  aBody->ToJSON(w);
 | 
						|
  w.End();
 | 
						|
 | 
						|
  nsCOMPtr<nsIPrincipal> principal =
 | 
						|
      nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
 | 
						|
  if (NS_WARN_IF(!principal)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  mozilla::ipc::PrincipalInfo principalInfo;
 | 
						|
  nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  mozilla::ipc::PBackgroundChild* actorChild =
 | 
						|
      mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
 | 
						|
 | 
						|
  PEndpointForReportChild* actor =
 | 
						|
      actorChild->SendPEndpointForReportConstructor(nsString(aGroupName),
 | 
						|
                                                    principalInfo);
 | 
						|
  if (NS_WARN_IF(!actor)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  ReportData data;
 | 
						|
  data.mType = aType;
 | 
						|
  data.mGroupName = aGroupName;
 | 
						|
  data.mURL = aURL;
 | 
						|
  data.mCreationTime = TimeStamp::Now();
 | 
						|
  data.mReportBodyJSON = reportBodyJSON;
 | 
						|
  data.mPrincipal = principal;
 | 
						|
  data.mFailures = 0;
 | 
						|
 | 
						|
  Navigator* navigator = aWindow->Navigator();
 | 
						|
  MOZ_ASSERT(navigator);
 | 
						|
 | 
						|
  IgnoredErrorResult error;
 | 
						|
  navigator->GetUserAgent(data.mUserAgent, CallerType::NonSystem, error);
 | 
						|
  if (NS_WARN_IF(error.Failed())) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  static_cast<EndpointForReportChild*>(actor)->Initialize(data);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
void ReportDeliver::Fetch(const ReportData& aReportData) {
 | 
						|
  if (!gReportDeliver) {
 | 
						|
    RefPtr<ReportDeliver> rd = new ReportDeliver();
 | 
						|
 | 
						|
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
 | 
						|
    if (NS_WARN_IF(!obs)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    obs->AddObserver(rd, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
 | 
						|
    gReportDeliver = rd;
 | 
						|
  }
 | 
						|
 | 
						|
  gReportDeliver->AppendReportData(aReportData);
 | 
						|
}
 | 
						|
 | 
						|
void ReportDeliver::AppendReportData(const ReportData& aReportData) {
 | 
						|
  if (aReportData.mFailures >
 | 
						|
      StaticPrefs::dom_reporting_delivering_maxFailures()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!mReportQueue.AppendElement(aReportData, fallible))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  while (mReportQueue.Length() >
 | 
						|
         StaticPrefs::dom_reporting_delivering_maxReports()) {
 | 
						|
    mReportQueue.RemoveElementAt(0);
 | 
						|
  }
 | 
						|
 | 
						|
  if (!mTimer) {
 | 
						|
    uint32_t timeout = StaticPrefs::dom_reporting_delivering_timeout() * 1000;
 | 
						|
    nsresult rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, timeout,
 | 
						|
                                          nsITimer::TYPE_ONE_SHOT);
 | 
						|
    Unused << NS_WARN_IF(NS_FAILED(rv));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ReportDeliver::Notify(nsITimer* aTimer) {
 | 
						|
  mTimer = nullptr;
 | 
						|
 | 
						|
  nsTArray<ReportData> reports;
 | 
						|
  reports.SwapElements(mReportQueue);
 | 
						|
 | 
						|
  // group reports by endpoint and nsIPrincipal
 | 
						|
  std::map<std::pair<nsCString, nsCOMPtr<nsIPrincipal>>, nsTArray<ReportData>>
 | 
						|
      reportsByPrincipal;
 | 
						|
  for (ReportData& report : reports) {
 | 
						|
    auto already_seen =
 | 
						|
        reportsByPrincipal.find({report.mEndpointURL, report.mPrincipal});
 | 
						|
    if (already_seen == reportsByPrincipal.end()) {
 | 
						|
      reportsByPrincipal.emplace(
 | 
						|
          std::make_pair(report.mEndpointURL, report.mPrincipal),
 | 
						|
          nsTArray<ReportData>({report}));
 | 
						|
    } else {
 | 
						|
      already_seen->second.AppendElement(report);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  for (auto& iter : reportsByPrincipal) {
 | 
						|
    std::pair<nsCString, nsCOMPtr<nsIPrincipal>> key = iter.first;
 | 
						|
    nsTArray<ReportData>& value = iter.second;
 | 
						|
    nsCString url = key.first;
 | 
						|
    nsCOMPtr<nsIPrincipal> principal = key.second;
 | 
						|
    nsAutoCString u(url);
 | 
						|
    SendReports(value, url, principal);
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ReportDeliver::Observe(nsISupports* aSubject, const char* aTopic,
 | 
						|
                       const char16_t* aData) {
 | 
						|
  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
 | 
						|
 | 
						|
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
 | 
						|
  if (NS_WARN_IF(!obs)) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 | 
						|
 | 
						|
  if (mTimer) {
 | 
						|
    mTimer->Cancel();
 | 
						|
    mTimer = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  gReportDeliver = nullptr;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
ReportDeliver::ReportDeliver() = default;
 | 
						|
 | 
						|
ReportDeliver::~ReportDeliver() = default;
 | 
						|
 | 
						|
NS_INTERFACE_MAP_BEGIN(ReportDeliver)
 | 
						|
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
 | 
						|
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
 | 
						|
  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
 | 
						|
NS_INTERFACE_MAP_END
 | 
						|
 | 
						|
NS_IMPL_ADDREF(ReportDeliver)
 | 
						|
NS_IMPL_RELEASE(ReportDeliver)
 | 
						|
 | 
						|
}  // namespace dom
 | 
						|
}  // namespace mozilla
 |