tor-browser

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

ReportDeliver.cpp (12003B)


      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/ReportDeliver.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "mozilla/JSONStringWriteFuncs.h"
     12 #include "mozilla/StaticPrefs_dom.h"
     13 #include "mozilla/dom/EndpointForReportChild.h"
     14 #include "mozilla/dom/Fetch.h"
     15 #include "mozilla/dom/Navigator.h"
     16 #include "mozilla/dom/Promise.h"
     17 #include "mozilla/dom/ReportBody.h"
     18 #include "mozilla/dom/Request.h"
     19 #include "mozilla/dom/RequestBinding.h"
     20 #include "mozilla/dom/Response.h"
     21 #include "mozilla/dom/RootedDictionary.h"
     22 #include "mozilla/ipc/BackgroundChild.h"
     23 #include "mozilla/ipc/PBackgroundChild.h"
     24 #include "mozilla/ipc/PBackgroundSharedTypes.h"
     25 #include "nsGlobalWindowInner.h"
     26 #include "nsIGlobalObject.h"
     27 #include "nsIXPConnect.h"
     28 #include "nsNetUtil.h"
     29 #include "nsStringStream.h"
     30 
     31 namespace mozilla::dom {
     32 
     33 namespace {
     34 
     35 StaticRefPtr<ReportDeliver> gReportDeliver;
     36 
     37 // This is the same value as the default value of
     38 // dom.min_timeout_value, so it's not that random.
     39 constexpr double gMinReportAgeInMs = 4.0;
     40 
     41 class ReportFetchHandler final : public PromiseNativeHandler {
     42 public:
     43  NS_DECL_ISUPPORTS
     44 
     45  explicit ReportFetchHandler(
     46      const nsTArray<ReportDeliver::ReportData>& aReportData)
     47      : mReports(aReportData.Clone()) {}
     48 
     49  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
     50                        ErrorResult& aRv) override {
     51    if (!gReportDeliver) {
     52      return;
     53    }
     54 
     55    if (NS_WARN_IF(!aValue.isObject())) {
     56      return;
     57    }
     58 
     59    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
     60    MOZ_ASSERT(obj);
     61 
     62    {
     63      Response* response = nullptr;
     64      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response, &obj, response)))) {
     65        return;
     66      }
     67 
     68      if (response->Status() == 410) {
     69        mozilla::ipc::PBackgroundChild* actorChild =
     70            mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
     71 
     72        for (const auto& report : mReports) {
     73          mozilla::ipc::PrincipalInfo principalInfo;
     74          nsresult rv =
     75              PrincipalToPrincipalInfo(report.mPrincipal, &principalInfo);
     76          if (NS_WARN_IF(NS_FAILED(rv))) {
     77            continue;
     78          }
     79 
     80          actorChild->SendRemoveEndpoint(report.mGroupName, report.mEndpointURL,
     81                                         principalInfo);
     82        }
     83      }
     84    }
     85  }
     86 
     87  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
     88                        ErrorResult& aRv) override {
     89    if (gReportDeliver) {
     90      for (auto& report : mReports) {
     91        ++report.mFailures;
     92        gReportDeliver->AppendReportData(report);
     93      }
     94    }
     95  }
     96 
     97 private:
     98  ~ReportFetchHandler() = default;
     99 
    100  nsTArray<ReportDeliver::ReportData> mReports;
    101 };
    102 
    103 NS_IMPL_ISUPPORTS0(ReportFetchHandler)
    104 
    105 class ReportJSONWriter final : public JSONWriter {
    106 public:
    107  explicit ReportJSONWriter(JSONStringWriteFunc<nsAutoCString>& aOutput)
    108      : JSONWriter(aOutput) {}
    109 
    110  void JSONProperty(const Span<const char>& aProperty,
    111                    const Span<const char>& aJSON) {
    112    Separator();
    113    PropertyNameAndColon(aProperty);
    114    mWriter.Write(aJSON);
    115  }
    116 };
    117 
    118 void SendReports(nsTArray<ReportDeliver::ReportData>& aReports,
    119                 const nsCString& aEndPointUrl, nsIPrincipal* aPrincipal) {
    120  if (NS_WARN_IF(aReports.IsEmpty())) {
    121    return;
    122  }
    123 
    124  nsIXPConnect* xpc = nsContentUtils::XPConnect();
    125  MOZ_ASSERT(xpc, "This should never be null!");
    126 
    127  nsCOMPtr<nsIGlobalObject> globalObject;
    128  {
    129    AutoJSAPI jsapi;
    130    jsapi.Init();
    131 
    132    JSContext* cx = jsapi.cx();
    133    JS::Rooted<JSObject*> sandbox(cx);
    134    nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
    135    if (NS_WARN_IF(NS_FAILED(rv))) {
    136      return;
    137    }
    138 
    139    // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
    140    // global.
    141    MOZ_ASSERT(JS_IsGlobalObject(sandbox));
    142 
    143    globalObject = xpc::NativeGlobal(sandbox);
    144  }
    145 
    146  if (NS_WARN_IF(!globalObject)) {
    147    return;
    148  }
    149 
    150  // The body
    151  JSONStringWriteFunc<nsAutoCString> body;
    152  ReportJSONWriter w(body);
    153 
    154  w.StartArrayElement();
    155  for (const auto& report : aReports) {
    156    MOZ_ASSERT(report.mPrincipal == aPrincipal);
    157    MOZ_ASSERT(report.mEndpointURL == aEndPointUrl);
    158    w.StartObjectElement();
    159    // It looks like in rare cases, TimeStamp::Now() may be the same
    160    // as report.mCreationTime, so we introduce a constant number to
    161    // make sure "age" is always not 0.
    162    w.IntProperty(
    163        "age",
    164        std::max((TimeStamp::Now() - report.mCreationTime).ToMilliseconds(),
    165                 gMinReportAgeInMs));
    166    w.StringProperty("type", NS_ConvertUTF16toUTF8(report.mType));
    167    w.StringProperty("url", NS_ConvertUTF16toUTF8(report.mURL));
    168    w.StringProperty("user_agent", NS_ConvertUTF16toUTF8(report.mUserAgent));
    169    w.JSONProperty(MakeStringSpan("body"),
    170                   Span<const char>(report.mReportBodyJSON.Data(),
    171                                    report.mReportBodyJSON.Length()));
    172    w.EndObject();
    173  }
    174  w.EndArray();
    175 
    176  // The body as stream
    177  nsCOMPtr<nsIInputStream> streamBody;
    178  nsresult rv =
    179      NS_NewCStringInputStream(getter_AddRefs(streamBody), body.StringCRef());
    180 
    181  // Headers
    182  IgnoredErrorResult error;
    183  RefPtr<InternalHeaders> internalHeaders =
    184      new InternalHeaders(HeadersGuardEnum::Request);
    185  internalHeaders->Set("Content-Type"_ns, "application/reports+json"_ns, error);
    186  if (NS_WARN_IF(error.Failed())) {
    187    return;
    188  }
    189 
    190  // URL and fragments
    191  nsCOMPtr<nsIURI> uri;
    192  rv = NS_NewURI(getter_AddRefs(uri), aEndPointUrl);
    193  if (NS_WARN_IF(NS_FAILED(rv))) {
    194    return;
    195  }
    196 
    197  nsCOMPtr<nsIURI> uriClone;
    198  rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriClone));
    199  if (NS_WARN_IF(NS_FAILED(rv))) {
    200    return;
    201  }
    202 
    203  nsAutoCString uriSpec;
    204  rv = uriClone->GetSpec(uriSpec);
    205  if (NS_WARN_IF(NS_FAILED(rv))) {
    206    return;
    207  }
    208 
    209  nsAutoCString uriFragment;
    210  rv = uri->GetRef(uriFragment);
    211  if (NS_WARN_IF(NS_FAILED(rv))) {
    212    return;
    213  }
    214 
    215  auto internalRequest = MakeSafeRefPtr<InternalRequest>(uriSpec, uriFragment);
    216 
    217  internalRequest->SetMethod("POST"_ns);
    218  internalRequest->SetBody(streamBody, body.StringCRef().Length());
    219  internalRequest->SetHeaders(internalHeaders);
    220  internalRequest->SetSkipServiceWorker();
    221  // TODO: internalRequest->SetContentPolicyType(TYPE_REPORT);
    222  internalRequest->SetMode(RequestMode::Cors);
    223  internalRequest->SetCredentialsMode(RequestCredentials::Same_origin);
    224 
    225  RefPtr<Request> request =
    226      new Request(globalObject, std::move(internalRequest), nullptr);
    227 
    228  RequestOrUTF8String fetchInput;
    229  fetchInput.SetAsRequest() = request;
    230 
    231  RootedDictionary<RequestInit> requestInit(RootingCx());
    232  RefPtr<Promise> promise = FetchRequest(globalObject, fetchInput, requestInit,
    233                                         CallerType::NonSystem, error);
    234  if (error.Failed()) {
    235    for (auto& report : aReports) {
    236      ++report.mFailures;
    237      if (gReportDeliver) {
    238        gReportDeliver->AppendReportData(report);
    239      }
    240    }
    241    return;
    242  }
    243 
    244  RefPtr<ReportFetchHandler> handler = new ReportFetchHandler(aReports);
    245  promise->AppendNativeHandler(handler);
    246 }
    247 
    248 }  // namespace
    249 
    250 /* static */
    251 void ReportDeliver::Record(nsPIDOMWindowInner* aWindow, const nsAString& aType,
    252                           const nsAString& aGroupName, const nsAString& aURL,
    253                           ReportBody* aBody) {
    254  MOZ_ASSERT(NS_IsMainThread());
    255  MOZ_ASSERT(aWindow);
    256  MOZ_ASSERT(aBody);
    257 
    258  JSONStringWriteFunc<nsAutoCString> reportBodyJSON;
    259  ReportJSONWriter w(reportBodyJSON);
    260 
    261  w.Start();
    262  aBody->ToJSON(w);
    263  w.End();
    264 
    265  nsCOMPtr<nsIPrincipal> principal =
    266      nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
    267  if (NS_WARN_IF(!principal)) {
    268    return;
    269  }
    270 
    271  mozilla::ipc::PrincipalInfo principalInfo;
    272  nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
    273  if (NS_WARN_IF(NS_FAILED(rv))) {
    274    return;
    275  }
    276 
    277  mozilla::ipc::PBackgroundChild* actorChild =
    278      mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
    279 
    280  PEndpointForReportChild* actor =
    281      actorChild->SendPEndpointForReportConstructor(nsString(aGroupName),
    282                                                    principalInfo);
    283  if (NS_WARN_IF(!actor)) {
    284    return;
    285  }
    286 
    287  ReportData data;
    288  data.mType = aType;
    289  data.mGroupName = aGroupName;
    290  data.mURL = aURL;
    291  data.mCreationTime = TimeStamp::Now();
    292  data.mReportBodyJSON = std::move(reportBodyJSON).StringRRef();
    293  data.mPrincipal = principal;
    294  data.mFailures = 0;
    295 
    296  Navigator* navigator = aWindow->Navigator();
    297  MOZ_ASSERT(navigator);
    298 
    299  IgnoredErrorResult error;
    300  navigator->GetUserAgent(data.mUserAgent, CallerType::NonSystem, error);
    301  if (NS_WARN_IF(error.Failed())) {
    302    return;
    303  }
    304 
    305  static_cast<EndpointForReportChild*>(actor)->Initialize(data);
    306 }
    307 
    308 /* static */
    309 void ReportDeliver::Fetch(const ReportData& aReportData) {
    310  if (!gReportDeliver) {
    311    RefPtr<ReportDeliver> rd = new ReportDeliver();
    312 
    313    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    314    if (NS_WARN_IF(!obs)) {
    315      return;
    316    }
    317 
    318    obs->AddObserver(rd, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
    319    gReportDeliver = rd;
    320  }
    321 
    322  gReportDeliver->AppendReportData(aReportData);
    323 }
    324 
    325 void ReportDeliver::AppendReportData(const ReportData& aReportData) {
    326  if (aReportData.mFailures >
    327      StaticPrefs::dom_reporting_delivering_maxFailures()) {
    328    return;
    329  }
    330 
    331  if (NS_WARN_IF(!mReportQueue.AppendElement(aReportData, fallible))) {
    332    return;
    333  }
    334 
    335  while (mReportQueue.Length() >
    336         StaticPrefs::dom_reporting_delivering_maxReports()) {
    337    mReportQueue.RemoveElementAt(0);
    338  }
    339 
    340  RefPtr<ReportDeliver> self{this};
    341  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
    342      "ReportDeliver::CallNotify", [self]() { self->Notify(); });
    343 
    344  NS_DispatchToCurrentThreadQueue(
    345      runnable.forget(), StaticPrefs::dom_reporting_delivering_timeout() * 1000,
    346      EventQueuePriority::Idle);
    347 }
    348 
    349 void ReportDeliver::Notify() {
    350  nsTArray<ReportData> reports = std::move(mReportQueue);
    351 
    352  // group reports by endpoint and nsIPrincipal
    353  std::map<std::pair<nsCString, nsCOMPtr<nsIPrincipal>>, nsTArray<ReportData>>
    354      reportsByPrincipal;
    355  for (ReportData& report : reports) {
    356    auto already_seen =
    357        reportsByPrincipal.find({report.mEndpointURL, report.mPrincipal});
    358    if (already_seen == reportsByPrincipal.end()) {
    359      reportsByPrincipal.emplace(
    360          std::make_pair(report.mEndpointURL, report.mPrincipal),
    361          nsTArray<ReportData>({report}));
    362    } else {
    363      already_seen->second.AppendElement(report);
    364    }
    365  }
    366 
    367  for (auto& iter : reportsByPrincipal) {
    368    std::pair<nsCString, nsCOMPtr<nsIPrincipal>> key = iter.first;
    369    nsTArray<ReportData>& value = iter.second;
    370    nsCString url = key.first;
    371    nsCOMPtr<nsIPrincipal> principal = key.second;
    372    nsAutoCString u(url);
    373    SendReports(value, url, principal);
    374  }
    375 }
    376 
    377 NS_IMETHODIMP
    378 ReportDeliver::GetName(nsACString& aName) {
    379  aName.AssignLiteral("ReportDeliver");
    380  return NS_OK;
    381 }
    382 
    383 NS_IMETHODIMP
    384 ReportDeliver::Observe(nsISupports* aSubject, const char* aTopic,
    385                       const char16_t* aData) {
    386  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
    387 
    388  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    389  if (NS_WARN_IF(!obs)) {
    390    return NS_OK;
    391  }
    392 
    393  obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
    394 
    395  gReportDeliver = nullptr;
    396  return NS_OK;
    397 }
    398 
    399 ReportDeliver::ReportDeliver() = default;
    400 
    401 ReportDeliver::~ReportDeliver() = default;
    402 
    403 NS_INTERFACE_MAP_BEGIN(ReportDeliver)
    404  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
    405  NS_INTERFACE_MAP_ENTRY(nsIObserver)
    406  NS_INTERFACE_MAP_ENTRY(nsINamed)
    407 NS_INTERFACE_MAP_END
    408 
    409 NS_IMPL_ADDREF(ReportDeliver)
    410 NS_IMPL_RELEASE(ReportDeliver)
    411 
    412 }  // namespace mozilla::dom