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/JSONStringWriteFuncs.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/dom/RootedDictionary.h"
19 #include "mozilla/ipc/BackgroundChild.h"
20 #include "mozilla/ipc/PBackgroundChild.h"
21 #include "mozilla/ipc/PBackgroundSharedTypes.h"
22 #include "nsGlobalWindowInner.h"
23 #include "nsIGlobalObject.h"
24 #include "nsIXPConnect.h"
25 #include "nsNetUtil.h"
26 #include "nsStringStream.h"
28 namespace mozilla::dom
{
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 class ReportJSONWriter final
: public JSONWriter
{
100 explicit ReportJSONWriter(JSONStringWriteFunc
<nsAutoCString
>& aOutput
)
101 : JSONWriter(aOutput
) {}
103 void JSONProperty(const Span
<const char>& aProperty
,
104 const Span
<const char>& aJSON
) {
106 PropertyNameAndColon(aProperty
);
107 mWriter
.Write(aJSON
);
111 void SendReports(nsTArray
<ReportDeliver::ReportData
>& aReports
,
112 const nsCString
& aEndPointUrl
, nsIPrincipal
* aPrincipal
) {
113 if (NS_WARN_IF(aReports
.IsEmpty())) {
117 nsIXPConnect
* xpc
= nsContentUtils::XPConnect();
118 MOZ_ASSERT(xpc
, "This should never be null!");
120 nsCOMPtr
<nsIGlobalObject
> globalObject
;
125 JSContext
* cx
= jsapi
.cx();
126 JS::Rooted
<JSObject
*> sandbox(cx
);
127 nsresult rv
= xpc
->CreateSandbox(cx
, aPrincipal
, sandbox
.address());
128 if (NS_WARN_IF(NS_FAILED(rv
))) {
132 // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
134 MOZ_ASSERT(JS_IsGlobalObject(sandbox
));
136 globalObject
= xpc::NativeGlobal(sandbox
);
139 if (NS_WARN_IF(!globalObject
)) {
144 JSONStringWriteFunc
<nsAutoCString
> body
;
145 ReportJSONWriter
w(body
);
147 w
.StartArrayElement();
148 for (const auto& report
: aReports
) {
149 MOZ_ASSERT(report
.mPrincipal
== aPrincipal
);
150 MOZ_ASSERT(report
.mEndpointURL
== aEndPointUrl
);
151 w
.StartObjectElement();
153 (TimeStamp::Now() - report
.mCreationTime
).ToMilliseconds());
154 w
.StringProperty("type", NS_ConvertUTF16toUTF8(report
.mType
));
155 w
.StringProperty("url", NS_ConvertUTF16toUTF8(report
.mURL
));
156 w
.StringProperty("user_agent", NS_ConvertUTF16toUTF8(report
.mUserAgent
));
157 w
.JSONProperty(MakeStringSpan("body"),
158 Span
<const char>(report
.mReportBodyJSON
.Data(),
159 report
.mReportBodyJSON
.Length()));
164 // The body as stream
165 nsCOMPtr
<nsIInputStream
> streamBody
;
167 NS_NewCStringInputStream(getter_AddRefs(streamBody
), body
.StringCRef());
170 IgnoredErrorResult error
;
171 RefPtr
<InternalHeaders
> internalHeaders
=
172 new InternalHeaders(HeadersGuardEnum::Request
);
173 internalHeaders
->Set("Content-Type"_ns
, "application/reports+json"_ns
, error
);
174 if (NS_WARN_IF(error
.Failed())) {
179 nsCOMPtr
<nsIURI
> uri
;
180 rv
= NS_NewURI(getter_AddRefs(uri
), aEndPointUrl
);
181 if (NS_WARN_IF(NS_FAILED(rv
))) {
185 nsCOMPtr
<nsIURI
> uriClone
;
186 rv
= NS_GetURIWithoutRef(uri
, getter_AddRefs(uriClone
));
187 if (NS_WARN_IF(NS_FAILED(rv
))) {
191 nsAutoCString uriSpec
;
192 rv
= uriClone
->GetSpec(uriSpec
);
193 if (NS_WARN_IF(NS_FAILED(rv
))) {
197 nsAutoCString uriFragment
;
198 rv
= uri
->GetRef(uriFragment
);
199 if (NS_WARN_IF(NS_FAILED(rv
))) {
203 auto internalRequest
= MakeSafeRefPtr
<InternalRequest
>(uriSpec
, uriFragment
);
205 internalRequest
->SetMethod("POST"_ns
);
206 internalRequest
->SetBody(streamBody
, body
.StringCRef().Length());
207 internalRequest
->SetHeaders(internalHeaders
);
208 internalRequest
->SetSkipServiceWorker();
209 // TODO: internalRequest->SetContentPolicyType(TYPE_REPORT);
210 internalRequest
->SetMode(RequestMode::Cors
);
211 internalRequest
->SetCredentialsMode(RequestCredentials::Include
);
213 RefPtr
<Request
> request
=
214 new Request(globalObject
, std::move(internalRequest
), nullptr);
216 RequestOrUSVString fetchInput
;
217 fetchInput
.SetAsRequest() = request
;
219 RootedDictionary
<RequestInit
> requestInit(RootingCx());
220 RefPtr
<Promise
> promise
= FetchRequest(globalObject
, fetchInput
, requestInit
,
221 CallerType::NonSystem
, error
);
222 if (error
.Failed()) {
223 for (auto& report
: aReports
) {
225 if (gReportDeliver
) {
226 gReportDeliver
->AppendReportData(report
);
232 RefPtr
<ReportFetchHandler
> handler
= new ReportFetchHandler(aReports
);
233 promise
->AppendNativeHandler(handler
);
239 void ReportDeliver::Record(nsPIDOMWindowInner
* aWindow
, const nsAString
& aType
,
240 const nsAString
& aGroupName
, const nsAString
& aURL
,
242 MOZ_ASSERT(NS_IsMainThread());
246 JSONStringWriteFunc
<nsAutoCString
> reportBodyJSON
;
247 ReportJSONWriter
w(reportBodyJSON
);
253 nsCOMPtr
<nsIPrincipal
> principal
=
254 nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
255 if (NS_WARN_IF(!principal
)) {
259 mozilla::ipc::PrincipalInfo principalInfo
;
260 nsresult rv
= PrincipalToPrincipalInfo(principal
, &principalInfo
);
261 if (NS_WARN_IF(NS_FAILED(rv
))) {
265 mozilla::ipc::PBackgroundChild
* actorChild
=
266 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
268 PEndpointForReportChild
* actor
=
269 actorChild
->SendPEndpointForReportConstructor(nsString(aGroupName
),
271 if (NS_WARN_IF(!actor
)) {
277 data
.mGroupName
= aGroupName
;
279 data
.mCreationTime
= TimeStamp::Now();
280 data
.mReportBodyJSON
= std::move(reportBodyJSON
).StringRRef();
281 data
.mPrincipal
= principal
;
284 Navigator
* navigator
= aWindow
->Navigator();
285 MOZ_ASSERT(navigator
);
287 IgnoredErrorResult error
;
288 navigator
->GetUserAgent(data
.mUserAgent
, CallerType::NonSystem
, error
);
289 if (NS_WARN_IF(error
.Failed())) {
293 static_cast<EndpointForReportChild
*>(actor
)->Initialize(data
);
297 void ReportDeliver::Fetch(const ReportData
& aReportData
) {
298 if (!gReportDeliver
) {
299 RefPtr
<ReportDeliver
> rd
= new ReportDeliver();
301 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
302 if (NS_WARN_IF(!obs
)) {
306 obs
->AddObserver(rd
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
310 gReportDeliver
->AppendReportData(aReportData
);
313 void ReportDeliver::AppendReportData(const ReportData
& aReportData
) {
314 if (aReportData
.mFailures
>
315 StaticPrefs::dom_reporting_delivering_maxFailures()) {
319 if (NS_WARN_IF(!mReportQueue
.AppendElement(aReportData
, fallible
))) {
323 while (mReportQueue
.Length() >
324 StaticPrefs::dom_reporting_delivering_maxReports()) {
325 mReportQueue
.RemoveElementAt(0);
329 uint32_t timeout
= StaticPrefs::dom_reporting_delivering_timeout() * 1000;
330 nsresult rv
= NS_NewTimerWithCallback(getter_AddRefs(mTimer
), this, timeout
,
331 nsITimer::TYPE_ONE_SHOT
);
332 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
337 ReportDeliver::Notify(nsITimer
* aTimer
) {
340 nsTArray
<ReportData
> reports
= std::move(mReportQueue
);
342 // group reports by endpoint and nsIPrincipal
343 std::map
<std::pair
<nsCString
, nsCOMPtr
<nsIPrincipal
>>, nsTArray
<ReportData
>>
345 for (ReportData
& report
: reports
) {
347 reportsByPrincipal
.find({report
.mEndpointURL
, report
.mPrincipal
});
348 if (already_seen
== reportsByPrincipal
.end()) {
349 reportsByPrincipal
.emplace(
350 std::make_pair(report
.mEndpointURL
, report
.mPrincipal
),
351 nsTArray
<ReportData
>({report
}));
353 already_seen
->second
.AppendElement(report
);
357 for (auto& iter
: reportsByPrincipal
) {
358 std::pair
<nsCString
, nsCOMPtr
<nsIPrincipal
>> key
= iter
.first
;
359 nsTArray
<ReportData
>& value
= iter
.second
;
360 nsCString url
= key
.first
;
361 nsCOMPtr
<nsIPrincipal
> principal
= key
.second
;
362 nsAutoCString
u(url
);
363 SendReports(value
, url
, principal
);
370 ReportDeliver::GetName(nsACString
& aName
) {
371 aName
.AssignLiteral("ReportDeliver");
376 ReportDeliver::Observe(nsISupports
* aSubject
, const char* aTopic
,
377 const char16_t
* aData
) {
378 MOZ_ASSERT(!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
));
380 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
381 if (NS_WARN_IF(!obs
)) {
385 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
392 gReportDeliver
= nullptr;
396 ReportDeliver::ReportDeliver() = default;
398 ReportDeliver::~ReportDeliver() = default;
400 NS_INTERFACE_MAP_BEGIN(ReportDeliver
)
401 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIObserver
)
402 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
403 NS_INTERFACE_MAP_ENTRY(nsITimerCallback
)
404 NS_INTERFACE_MAP_ENTRY(nsINamed
)
407 NS_IMPL_ADDREF(ReportDeliver
)
408 NS_IMPL_RELEASE(ReportDeliver
)
410 } // namespace mozilla::dom