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/. */
9 #include "mozilla/JSONStringWriteFuncs.h"
10 #include "mozilla/StaticPrefs_dom.h"
11 #include "mozilla/dom/EndpointForReportChild.h"
12 #include "mozilla/dom/Fetch.h"
13 #include "mozilla/dom/Navigator.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/ReportBody.h"
16 #include "mozilla/dom/ReportDeliver.h"
17 #include "mozilla/dom/Request.h"
18 #include "mozilla/dom/RequestBinding.h"
19 #include "mozilla/dom/Response.h"
20 #include "mozilla/dom/RootedDictionary.h"
21 #include "mozilla/ipc/BackgroundChild.h"
22 #include "mozilla/ipc/PBackgroundChild.h"
23 #include "mozilla/ipc/PBackgroundSharedTypes.h"
24 #include "nsGlobalWindowInner.h"
25 #include "nsIGlobalObject.h"
26 #include "nsIXPConnect.h"
27 #include "nsNetUtil.h"
28 #include "nsStringStream.h"
30 namespace mozilla::dom
{
34 StaticRefPtr
<ReportDeliver
> gReportDeliver
;
36 // This is the same value as the default value of
37 // dom.min_timeout_value, so it's not that random.
38 constexpr double gMinReportAgeInMs
= 4.0;
40 class ReportFetchHandler final
: public PromiseNativeHandler
{
44 explicit ReportFetchHandler(
45 const nsTArray
<ReportDeliver::ReportData
>& aReportData
)
46 : mReports(aReportData
.Clone()) {}
48 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
49 ErrorResult
& aRv
) override
{
50 if (!gReportDeliver
) {
54 if (NS_WARN_IF(!aValue
.isObject())) {
58 JS::Rooted
<JSObject
*> obj(aCx
, &aValue
.toObject());
62 Response
* response
= nullptr;
63 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response
, &obj
, response
)))) {
67 if (response
->Status() == 410) {
68 mozilla::ipc::PBackgroundChild
* actorChild
=
69 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
71 for (const auto& report
: mReports
) {
72 mozilla::ipc::PrincipalInfo principalInfo
;
74 PrincipalToPrincipalInfo(report
.mPrincipal
, &principalInfo
);
75 if (NS_WARN_IF(NS_FAILED(rv
))) {
79 actorChild
->SendRemoveEndpoint(report
.mGroupName
, report
.mEndpointURL
,
86 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
87 ErrorResult
& aRv
) override
{
89 for (auto& report
: mReports
) {
91 gReportDeliver
->AppendReportData(report
);
97 ~ReportFetchHandler() = default;
99 nsTArray
<ReportDeliver::ReportData
> mReports
;
102 NS_IMPL_ISUPPORTS0(ReportFetchHandler
)
104 class ReportJSONWriter final
: public JSONWriter
{
106 explicit ReportJSONWriter(JSONStringWriteFunc
<nsAutoCString
>& aOutput
)
107 : JSONWriter(aOutput
) {}
109 void JSONProperty(const Span
<const char>& aProperty
,
110 const Span
<const char>& aJSON
) {
112 PropertyNameAndColon(aProperty
);
113 mWriter
.Write(aJSON
);
117 void SendReports(nsTArray
<ReportDeliver::ReportData
>& aReports
,
118 const nsCString
& aEndPointUrl
, nsIPrincipal
* aPrincipal
) {
119 if (NS_WARN_IF(aReports
.IsEmpty())) {
123 nsIXPConnect
* xpc
= nsContentUtils::XPConnect();
124 MOZ_ASSERT(xpc
, "This should never be null!");
126 nsCOMPtr
<nsIGlobalObject
> globalObject
;
131 JSContext
* cx
= jsapi
.cx();
132 JS::Rooted
<JSObject
*> sandbox(cx
);
133 nsresult rv
= xpc
->CreateSandbox(cx
, aPrincipal
, sandbox
.address());
134 if (NS_WARN_IF(NS_FAILED(rv
))) {
138 // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
140 MOZ_ASSERT(JS_IsGlobalObject(sandbox
));
142 globalObject
= xpc::NativeGlobal(sandbox
);
145 if (NS_WARN_IF(!globalObject
)) {
150 JSONStringWriteFunc
<nsAutoCString
> body
;
151 ReportJSONWriter
w(body
);
153 w
.StartArrayElement();
154 for (const auto& report
: aReports
) {
155 MOZ_ASSERT(report
.mPrincipal
== aPrincipal
);
156 MOZ_ASSERT(report
.mEndpointURL
== aEndPointUrl
);
157 w
.StartObjectElement();
158 // It looks like in rare cases, TimeStamp::Now() may be the same
159 // as report.mCreationTime, so we introduce a constant number to
160 // make sure "age" is always not 0.
163 std::max((TimeStamp::Now() - report
.mCreationTime
).ToMilliseconds(),
165 w
.StringProperty("type", NS_ConvertUTF16toUTF8(report
.mType
));
166 w
.StringProperty("url", NS_ConvertUTF16toUTF8(report
.mURL
));
167 w
.StringProperty("user_agent", NS_ConvertUTF16toUTF8(report
.mUserAgent
));
168 w
.JSONProperty(MakeStringSpan("body"),
169 Span
<const char>(report
.mReportBodyJSON
.Data(),
170 report
.mReportBodyJSON
.Length()));
175 // The body as stream
176 nsCOMPtr
<nsIInputStream
> streamBody
;
178 NS_NewCStringInputStream(getter_AddRefs(streamBody
), body
.StringCRef());
181 IgnoredErrorResult error
;
182 RefPtr
<InternalHeaders
> internalHeaders
=
183 new InternalHeaders(HeadersGuardEnum::Request
);
184 internalHeaders
->Set("Content-Type"_ns
, "application/reports+json"_ns
, error
);
185 if (NS_WARN_IF(error
.Failed())) {
190 nsCOMPtr
<nsIURI
> uri
;
191 rv
= NS_NewURI(getter_AddRefs(uri
), aEndPointUrl
);
192 if (NS_WARN_IF(NS_FAILED(rv
))) {
196 nsCOMPtr
<nsIURI
> uriClone
;
197 rv
= NS_GetURIWithoutRef(uri
, getter_AddRefs(uriClone
));
198 if (NS_WARN_IF(NS_FAILED(rv
))) {
202 nsAutoCString uriSpec
;
203 rv
= uriClone
->GetSpec(uriSpec
);
204 if (NS_WARN_IF(NS_FAILED(rv
))) {
208 nsAutoCString uriFragment
;
209 rv
= uri
->GetRef(uriFragment
);
210 if (NS_WARN_IF(NS_FAILED(rv
))) {
214 auto internalRequest
= MakeSafeRefPtr
<InternalRequest
>(uriSpec
, uriFragment
);
216 internalRequest
->SetMethod("POST"_ns
);
217 internalRequest
->SetBody(streamBody
, body
.StringCRef().Length());
218 internalRequest
->SetHeaders(internalHeaders
);
219 internalRequest
->SetSkipServiceWorker();
220 // TODO: internalRequest->SetContentPolicyType(TYPE_REPORT);
221 internalRequest
->SetMode(RequestMode::Cors
);
222 internalRequest
->SetCredentialsMode(RequestCredentials::Same_origin
);
224 RefPtr
<Request
> request
=
225 new Request(globalObject
, std::move(internalRequest
), nullptr);
227 RequestOrUTF8String fetchInput
;
228 fetchInput
.SetAsRequest() = request
;
230 RootedDictionary
<RequestInit
> requestInit(RootingCx());
231 RefPtr
<Promise
> promise
= FetchRequest(globalObject
, fetchInput
, requestInit
,
232 CallerType::NonSystem
, error
);
233 if (error
.Failed()) {
234 for (auto& report
: aReports
) {
236 if (gReportDeliver
) {
237 gReportDeliver
->AppendReportData(report
);
243 RefPtr
<ReportFetchHandler
> handler
= new ReportFetchHandler(aReports
);
244 promise
->AppendNativeHandler(handler
);
250 void ReportDeliver::Record(nsPIDOMWindowInner
* aWindow
, const nsAString
& aType
,
251 const nsAString
& aGroupName
, const nsAString
& aURL
,
253 MOZ_ASSERT(NS_IsMainThread());
257 JSONStringWriteFunc
<nsAutoCString
> reportBodyJSON
;
258 ReportJSONWriter
w(reportBodyJSON
);
264 nsCOMPtr
<nsIPrincipal
> principal
=
265 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
266 if (NS_WARN_IF(!principal
)) {
270 mozilla::ipc::PrincipalInfo principalInfo
;
271 nsresult rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
272 if (NS_WARN_IF(NS_FAILED(rv
))) {
276 mozilla::ipc::PBackgroundChild
* actorChild
=
277 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
279 PEndpointForReportChild
* actor
=
280 actorChild
->SendPEndpointForReportConstructor(nsString(aGroupName
),
282 if (NS_WARN_IF(!actor
)) {
288 data
.mGroupName
= aGroupName
;
290 data
.mCreationTime
= TimeStamp::Now();
291 data
.mReportBodyJSON
= std::move(reportBodyJSON
).StringRRef();
292 data
.mPrincipal
= principal
;
295 Navigator
* navigator
= aWindow
->Navigator();
296 MOZ_ASSERT(navigator
);
298 IgnoredErrorResult error
;
299 navigator
->GetUserAgent(data
.mUserAgent
, CallerType::NonSystem
, error
);
300 if (NS_WARN_IF(error
.Failed())) {
304 static_cast<EndpointForReportChild
*>(actor
)->Initialize(data
);
308 void ReportDeliver::Fetch(const ReportData
& aReportData
) {
309 if (!gReportDeliver
) {
310 RefPtr
<ReportDeliver
> rd
= new ReportDeliver();
312 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
313 if (NS_WARN_IF(!obs
)) {
317 obs
->AddObserver(rd
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
321 gReportDeliver
->AppendReportData(aReportData
);
324 void ReportDeliver::AppendReportData(const ReportData
& aReportData
) {
325 if (aReportData
.mFailures
>
326 StaticPrefs::dom_reporting_delivering_maxFailures()) {
330 if (NS_WARN_IF(!mReportQueue
.AppendElement(aReportData
, fallible
))) {
334 while (mReportQueue
.Length() >
335 StaticPrefs::dom_reporting_delivering_maxReports()) {
336 mReportQueue
.RemoveElementAt(0);
339 RefPtr
<ReportDeliver
> self
{this};
340 nsCOMPtr
<nsIRunnable
> runnable
= NS_NewRunnableFunction(
341 "ReportDeliver::CallNotify", [self
]() { self
->Notify(); });
343 NS_DispatchToCurrentThreadQueue(
344 runnable
.forget(), StaticPrefs::dom_reporting_delivering_timeout() * 1000,
345 EventQueuePriority::Idle
);
348 void ReportDeliver::Notify() {
349 nsTArray
<ReportData
> reports
= std::move(mReportQueue
);
351 // group reports by endpoint and nsIPrincipal
352 std::map
<std::pair
<nsCString
, nsCOMPtr
<nsIPrincipal
>>, nsTArray
<ReportData
>>
354 for (ReportData
& report
: reports
) {
356 reportsByPrincipal
.find({report
.mEndpointURL
, report
.mPrincipal
});
357 if (already_seen
== reportsByPrincipal
.end()) {
358 reportsByPrincipal
.emplace(
359 std::make_pair(report
.mEndpointURL
, report
.mPrincipal
),
360 nsTArray
<ReportData
>({report
}));
362 already_seen
->second
.AppendElement(report
);
366 for (auto& iter
: reportsByPrincipal
) {
367 std::pair
<nsCString
, nsCOMPtr
<nsIPrincipal
>> key
= iter
.first
;
368 nsTArray
<ReportData
>& value
= iter
.second
;
369 nsCString url
= key
.first
;
370 nsCOMPtr
<nsIPrincipal
> principal
= key
.second
;
371 nsAutoCString
u(url
);
372 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
);
394 gReportDeliver
= nullptr;
398 ReportDeliver::ReportDeliver() = default;
400 ReportDeliver::~ReportDeliver() = default;
402 NS_INTERFACE_MAP_BEGIN(ReportDeliver
)
403 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIObserver
)
404 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
405 NS_INTERFACE_MAP_ENTRY(nsINamed
)
408 NS_IMPL_ADDREF(ReportDeliver
)
409 NS_IMPL_RELEASE(ReportDeliver
)
411 } // namespace mozilla::dom