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/. */
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"
32 StaticRefPtr
<ReportDeliver
> gReportDeliver
;
34 class ReportFetchHandler final
: public PromiseNativeHandler
{
38 explicit ReportFetchHandler(
39 const nsTArray
<ReportDeliver::ReportData
>& aReportData
)
40 : mReports(aReportData
.Clone()) {}
42 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
43 ErrorResult
& aRv
) override
{
44 if (!gReportDeliver
) {
48 if (NS_WARN_IF(!aValue
.isObject())) {
52 JS::Rooted
<JSObject
*> obj(aCx
, &aValue
.toObject());
56 Response
* response
= nullptr;
57 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response
, &obj
, response
)))) {
61 if (response
->Status() == 410) {
62 mozilla::ipc::PBackgroundChild
* actorChild
=
63 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
65 for (const auto& report
: mReports
) {
66 mozilla::ipc::PrincipalInfo principalInfo
;
68 PrincipalToPrincipalInfo(report
.mPrincipal
, &principalInfo
);
69 if (NS_WARN_IF(NS_FAILED(rv
))) {
73 actorChild
->SendRemoveEndpoint(report
.mGroupName
, report
.mEndpointURL
,
80 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
81 ErrorResult
& aRv
) override
{
83 for (auto& report
: mReports
) {
85 gReportDeliver
->AppendReportData(report
);
91 ~ReportFetchHandler() = default;
93 nsTArray
<ReportDeliver::ReportData
> mReports
;
96 NS_IMPL_ISUPPORTS0(ReportFetchHandler
)
98 struct StringWriteFunc final
: public JSONWriteFunc
{
100 mBuffer
; // The lifetime of the struct must be bound to the buffer
101 explicit StringWriteFunc(nsACString
& aBuffer
) : mBuffer(aBuffer
) {}
103 void Write(const Span
<const char>& aStr
) override
{ mBuffer
.Append(aStr
); }
106 class ReportJSONWriter final
: public JSONWriter
{
108 explicit ReportJSONWriter(nsACString
& aOutput
)
109 : JSONWriter(MakeUnique
<StringWriteFunc
>(aOutput
)) {}
111 void JSONProperty(const Span
<const char>& aProperty
,
112 const Span
<const char>& aJSON
) {
114 PropertyNameAndColon(aProperty
);
115 mWriter
->Write(aJSON
);
119 void SendReports(nsTArray
<ReportDeliver::ReportData
>& aReports
,
120 const nsCString
& aEndPointUrl
, nsIPrincipal
* aPrincipal
) {
121 if (NS_WARN_IF(aReports
.IsEmpty())) {
125 nsIXPConnect
* xpc
= nsContentUtils::XPConnect();
126 MOZ_ASSERT(xpc
, "This should never be null!");
128 nsCOMPtr
<nsIGlobalObject
> globalObject
;
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
))) {
140 // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
142 MOZ_ASSERT(JS_IsGlobalObject(sandbox
));
144 globalObject
= xpc::NativeGlobal(sandbox
);
147 if (NS_WARN_IF(!globalObject
)) {
153 ReportJSONWriter
w(body
);
155 w
.StartArrayElement();
156 for (const auto& report
: aReports
) {
157 MOZ_ASSERT(report
.mPrincipal
== aPrincipal
);
158 MOZ_ASSERT(report
.mEndpointURL
== aEndPointUrl
);
159 w
.StartObjectElement();
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()));
172 // The body as stream
173 nsCOMPtr
<nsIInputStream
> streamBody
;
174 nsresult rv
= NS_NewCStringInputStream(getter_AddRefs(streamBody
), body
);
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())) {
186 nsCOMPtr
<nsIURI
> uri
;
187 rv
= NS_NewURI(getter_AddRefs(uri
), aEndPointUrl
);
188 if (NS_WARN_IF(NS_FAILED(rv
))) {
192 nsCOMPtr
<nsIURI
> uriClone
;
193 rv
= NS_GetURIWithoutRef(uri
, getter_AddRefs(uriClone
));
194 if (NS_WARN_IF(NS_FAILED(rv
))) {
198 nsAutoCString uriSpec
;
199 rv
= uriClone
->GetSpec(uriSpec
);
200 if (NS_WARN_IF(NS_FAILED(rv
))) {
204 nsAutoCString uriFragment
;
205 rv
= uri
->GetRef(uriFragment
);
206 if (NS_WARN_IF(NS_FAILED(rv
))) {
210 auto internalRequest
= MakeSafeRefPtr
<InternalRequest
>(uriSpec
, uriFragment
);
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
);
220 RefPtr
<Request
> request
=
221 new Request(globalObject
, std::move(internalRequest
), nullptr);
223 RequestOrUSVString fetchInput
;
224 fetchInput
.SetAsRequest() = request
;
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
) {
232 if (gReportDeliver
) {
233 gReportDeliver
->AppendReportData(report
);
239 RefPtr
<ReportFetchHandler
> handler
= new ReportFetchHandler(aReports
);
240 promise
->AppendNativeHandler(handler
);
246 void ReportDeliver::Record(nsPIDOMWindowInner
* aWindow
, const nsAString
& aType
,
247 const nsAString
& aGroupName
, const nsAString
& aURL
,
249 MOZ_ASSERT(NS_IsMainThread());
253 nsAutoCString reportBodyJSON
;
254 ReportJSONWriter
w(reportBodyJSON
);
260 nsCOMPtr
<nsIPrincipal
> principal
=
261 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
262 if (NS_WARN_IF(!principal
)) {
266 mozilla::ipc::PrincipalInfo principalInfo
;
267 nsresult rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
268 if (NS_WARN_IF(NS_FAILED(rv
))) {
272 mozilla::ipc::PBackgroundChild
* actorChild
=
273 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
275 PEndpointForReportChild
* actor
=
276 actorChild
->SendPEndpointForReportConstructor(nsString(aGroupName
),
278 if (NS_WARN_IF(!actor
)) {
284 data
.mGroupName
= aGroupName
;
286 data
.mCreationTime
= TimeStamp::Now();
287 data
.mReportBodyJSON
= reportBodyJSON
;
288 data
.mPrincipal
= principal
;
291 Navigator
* navigator
= aWindow
->Navigator();
292 MOZ_ASSERT(navigator
);
294 IgnoredErrorResult error
;
295 navigator
->GetUserAgent(data
.mUserAgent
, CallerType::NonSystem
, error
);
296 if (NS_WARN_IF(error
.Failed())) {
300 static_cast<EndpointForReportChild
*>(actor
)->Initialize(data
);
304 void ReportDeliver::Fetch(const ReportData
& aReportData
) {
305 if (!gReportDeliver
) {
306 RefPtr
<ReportDeliver
> rd
= new ReportDeliver();
308 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
309 if (NS_WARN_IF(!obs
)) {
313 obs
->AddObserver(rd
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
317 gReportDeliver
->AppendReportData(aReportData
);
320 void ReportDeliver::AppendReportData(const ReportData
& aReportData
) {
321 if (aReportData
.mFailures
>
322 StaticPrefs::dom_reporting_delivering_maxFailures()) {
326 if (NS_WARN_IF(!mReportQueue
.AppendElement(aReportData
, fallible
))) {
330 while (mReportQueue
.Length() >
331 StaticPrefs::dom_reporting_delivering_maxReports()) {
332 mReportQueue
.RemoveElementAt(0);
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
));
344 ReportDeliver::Notify(nsITimer
* aTimer
) {
347 nsTArray
<ReportData
> reports
= std::move(mReportQueue
);
349 // group reports by endpoint and nsIPrincipal
350 std::map
<std::pair
<nsCString
, nsCOMPtr
<nsIPrincipal
>>, nsTArray
<ReportData
>>
352 for (ReportData
& report
: reports
) {
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
}));
360 already_seen
->second
.AppendElement(report
);
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
);
377 ReportDeliver::GetName(nsACString
& aName
) {
378 aName
.AssignLiteral("ReportDeliver");
383 ReportDeliver::Observe(nsISupports
* aSubject
, const char* aTopic
,
384 const char16_t
* aData
) {
385 MOZ_ASSERT(!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
));
387 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
388 if (NS_WARN_IF(!obs
)) {
392 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
399 gReportDeliver
= nullptr;
403 ReportDeliver::ReportDeliver() = default;
405 ReportDeliver::~ReportDeliver() = default;
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
)
414 NS_IMPL_ADDREF(ReportDeliver
)
415 NS_IMPL_RELEASE(ReportDeliver
)
418 } // namespace mozilla