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