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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "nsISupportsImpl.h"
11 #include "nsNetUtil.h"
12 #include "nsPIDOMWindow.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/ErrorResult.h"
15 #include "mozilla/HoldDropJSObjects.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/FetchBinding.h"
18 #include "mozilla/dom/ResponseBinding.h"
19 #include "mozilla/dom/Headers.h"
20 #include "mozilla/dom/Promise.h"
21 #include "mozilla/dom/URL.h"
22 #include "mozilla/dom/WorkerPrivate.h"
24 #include "nsDOMString.h"
26 #include "BodyExtractor.h"
27 #include "FetchStreamReader.h"
28 #include "InternalResponse.h"
30 #include "mozilla/dom/ReadableStreamDefaultReader.h"
32 namespace mozilla::dom
{
34 NS_IMPL_ADDREF_INHERITED(Response
, FetchBody
<Response
>)
35 NS_IMPL_RELEASE_INHERITED(Response
, FetchBody
<Response
>)
37 NS_IMPL_CYCLE_COLLECTION_CLASS(Response
)
39 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
40 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner
)
41 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders
)
42 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalImpl
)
43 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader
)
44 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
45 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
48 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner
)
49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders
)
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalImpl
)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader
)
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
54 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
55 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
56 NS_IMPL_CYCLE_COLLECTION_TRACE_END
58 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response
)
59 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
60 NS_INTERFACE_MAP_END_INHERITING(FetchBody
<Response
>)
62 Response::Response(nsIGlobalObject
* aGlobal
,
63 SafeRefPtr
<InternalResponse
> aInternalResponse
,
64 AbortSignalImpl
* aSignalImpl
)
65 : FetchBody
<Response
>(aGlobal
),
66 mInternalResponse(std::move(aInternalResponse
)),
67 mSignalImpl(aSignalImpl
) {
69 mInternalResponse
->Headers()->Guard() == HeadersGuardEnum::Immutable
||
70 mInternalResponse
->Headers()->Guard() == HeadersGuardEnum::Response
);
72 mozilla::HoldJSObjects(this);
75 Response::~Response() { mozilla::DropJSObjects(this); }
78 already_AddRefed
<Response
> Response::Error(const GlobalObject
& aGlobal
) {
79 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
80 RefPtr
<Response
> r
= new Response(
81 global
, InternalResponse::NetworkError(NS_ERROR_FAILURE
), nullptr);
86 already_AddRefed
<Response
> Response::Redirect(const GlobalObject
& aGlobal
,
87 const nsACString
& aUrl
,
90 nsAutoCString parsedURL
;
92 if (NS_IsMainThread()) {
93 nsIURI
* baseURI
= nullptr;
94 nsCOMPtr
<nsPIDOMWindowInner
> inner(
95 do_QueryInterface(aGlobal
.GetAsSupports()));
96 if (Document
* doc
= inner
? inner
->GetExtantDoc() : nullptr) {
97 baseURI
= doc
->GetBaseURI();
99 nsCOMPtr
<nsIURI
> resolvedURI
;
101 NS_NewURI(getter_AddRefs(resolvedURI
), aUrl
, nullptr, baseURI
);
102 if (NS_WARN_IF(NS_FAILED(rv
))) {
103 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(aUrl
);
107 rv
= resolvedURI
->GetSpec(parsedURL
);
108 if (NS_WARN_IF(NS_FAILED(rv
))) {
109 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(aUrl
);
113 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
115 worker
->AssertIsOnWorkerThread();
117 const auto& baseURL
= worker
->GetLocationInfo().mHref
;
119 URL::Constructor(aGlobal
.GetAsSupports(), aUrl
, baseURL
, aRv
);
123 url
->GetHref(parsedURL
);
126 if (aStatus
!= 301 && aStatus
!= 302 && aStatus
!= 303 && aStatus
!= 307 &&
128 aRv
.ThrowRangeError("Invalid redirect status code.");
132 // We can't just pass nullptr for our null-valued Nullable, because the
133 // fetch::ResponseBodyInit is a non-temporary type due to the MOZ_RAII
134 // annotations on some of its members.
135 Nullable
<fetch::ResponseBodyInit
> body
;
137 init
.mStatus
= aStatus
;
138 init
.mStatusText
.AssignASCII("");
139 RefPtr
<Response
> r
= Response::Constructor(aGlobal
, body
, init
, aRv
);
140 if (NS_WARN_IF(aRv
.Failed())) {
144 r
->GetInternalHeaders()->Set("Location"_ns
, parsedURL
, aRv
);
145 if (NS_WARN_IF(aRv
.Failed())) {
148 r
->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable
, aRv
);
149 MOZ_ASSERT(!aRv
.Failed());
154 /* static */ already_AddRefed
<Response
> Response::CreateAndInitializeAResponse(
155 const GlobalObject
& aGlobal
, const Nullable
<fetch::ResponseBodyInit
>& aBody
,
156 const nsACString
& aDefaultContentType
, const ResponseInit
& aInit
,
158 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
160 if (NS_WARN_IF(!global
)) {
161 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
165 // Initialize a response, Step 1.
166 if (aInit
.mStatus
< 200 || aInit
.mStatus
> 599) {
167 aRv
.ThrowRangeError("Invalid response status code.");
171 // Initialize a response, Step 2: Check if the status text contains illegal
173 nsACString::const_iterator start
, end
;
174 aInit
.mStatusText
.BeginReading(start
);
175 aInit
.mStatusText
.EndReading(end
);
176 if (FindCharInReadable('\r', start
, end
)) {
177 aRv
.ThrowTypeError
<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR
>();
180 // Reset iterator since FindCharInReadable advances it.
181 aInit
.mStatusText
.BeginReading(start
);
182 if (FindCharInReadable('\n', start
, end
)) {
183 aRv
.ThrowTypeError
<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR
>();
187 // Initialize a response, Step 3-4.
188 SafeRefPtr
<InternalResponse
> internalResponse
=
189 MakeSafeRefPtr
<InternalResponse
>(aInit
.mStatus
, aInit
.mStatusText
);
191 UniquePtr
<mozilla::ipc::PrincipalInfo
> principalInfo
;
193 // Grab a valid channel info from the global so this response is 'valid' for
195 if (NS_IsMainThread()) {
197 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
);
199 Document
* doc
= window
->GetExtantDoc();
201 info
.InitFromDocument(doc
);
203 principalInfo
.reset(new mozilla::ipc::PrincipalInfo());
205 PrincipalToPrincipalInfo(doc
->NodePrincipal(), principalInfo
.get());
206 if (NS_WARN_IF(NS_FAILED(rv
))) {
207 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
211 internalResponse
->InitChannelInfo(info
);
212 } else if (global
->PrincipalOrNull()->IsSystemPrincipal()) {
213 info
.InitFromChromeGlobal(global
);
215 internalResponse
->InitChannelInfo(info
);
219 * The channel info is left uninitialized if neither the above `if` nor
220 * `else if` statements are executed; this could be because we're in a
221 * WebExtensions content script, where the global (i.e. `global`) is a
222 * wrapper, and the principal is an expanded principal. In this case,
223 * as far as I can tell, there's no way to get the security info, but we'd
224 * like the `Response` to be successfully constructed.
227 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
229 internalResponse
->InitChannelInfo(worker
->GetChannelInfo());
231 MakeUnique
<mozilla::ipc::PrincipalInfo
>(worker
->GetPrincipalInfo());
234 internalResponse
->SetPrincipalInfo(std::move(principalInfo
));
237 new Response(global
, internalResponse
.clonePtr(), nullptr);
239 if (aInit
.mHeaders
.WasPassed()) {
240 internalResponse
->Headers()->Clear();
242 // Instead of using Fill, create an object to allow the constructor to
243 // unwrap the HeadersInit.
244 RefPtr
<Headers
> headers
=
245 Headers::Create(global
, aInit
.mHeaders
.Value(), aRv
);
250 internalResponse
->Headers()->Fill(*headers
->GetInternalHeaders(), aRv
);
251 if (NS_WARN_IF(aRv
.Failed())) {
256 if (!aBody
.IsNull()) {
257 if (aInit
.mStatus
== 204 || aInit
.mStatus
== 205 || aInit
.mStatus
== 304) {
258 aRv
.ThrowTypeError("Response body is given with a null body status.");
262 nsCString contentTypeWithCharset
;
263 contentTypeWithCharset
.SetIsVoid(true);
264 nsCOMPtr
<nsIInputStream
> bodyStream
;
265 int64_t bodySize
= InternalResponse::UNKNOWN_BODY_SIZE
;
267 const fetch::ResponseBodyInit
& body
= aBody
.Value();
268 if (body
.IsReadableStream()) {
269 JSContext
* cx
= aGlobal
.Context();
270 aRv
.MightThrowJSException();
272 ReadableStream
& readableStream
= body
.GetAsReadableStream();
274 if (readableStream
.Locked() || readableStream
.Disturbed()) {
275 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
279 r
->SetReadableStreamBody(cx
, &readableStream
);
281 // If this is a DOM generated ReadableStream, we can extract the
282 // inputStream directly.
283 if (nsIInputStream
* underlyingSource
=
284 readableStream
.MaybeGetInputStreamIfUnread()) {
285 bodyStream
= underlyingSource
;
287 // If this is a JS-created ReadableStream, let's create a
288 // FetchStreamReader.
289 aRv
= FetchStreamReader::Create(aGlobal
.Context(), global
,
290 getter_AddRefs(r
->mFetchStreamReader
),
291 getter_AddRefs(bodyStream
));
292 if (NS_WARN_IF(aRv
.Failed())) {
298 aRv
= ExtractByteStreamFromBody(body
, getter_AddRefs(bodyStream
),
299 contentTypeWithCharset
, size
);
300 if (NS_WARN_IF(aRv
.Failed())) {
304 if (!aDefaultContentType
.IsVoid()) {
305 contentTypeWithCharset
= aDefaultContentType
;
311 internalResponse
->SetBody(bodyStream
, bodySize
);
313 if (!contentTypeWithCharset
.IsVoid() &&
314 !internalResponse
->Headers()->Has("Content-Type"_ns
, aRv
)) {
315 // Ignore Append() failing here.
317 internalResponse
->Headers()->Append("Content-Type"_ns
,
318 contentTypeWithCharset
, error
);
319 error
.SuppressException();
331 already_AddRefed
<Response
> Response::CreateFromJson(const GlobalObject
& aGlobal
,
333 JS::Handle
<JS::Value
> aData
,
334 const ResponseInit
& aInit
,
336 aRv
.MightThrowJSException();
337 nsAutoString serializedValue
;
338 if (!nsContentUtils::StringifyJSON(aCx
, aData
, serializedValue
,
339 UndefinedIsVoidString
)) {
340 aRv
.StealExceptionFromJSContext(aCx
);
343 if (serializedValue
.IsVoid()) {
344 aRv
.ThrowTypeError
<MSG_JSON_INVALID_VALUE
>();
347 Nullable
<fetch::ResponseBodyInit
> body
;
348 body
.SetValue().SetAsUSVString().ShareOrDependUpon(serializedValue
);
349 return CreateAndInitializeAResponse(aGlobal
, body
, "application/json"_ns
,
354 already_AddRefed
<Response
> Response::Constructor(
355 const GlobalObject
& aGlobal
, const Nullable
<fetch::ResponseBodyInit
>& aBody
,
356 const ResponseInit
& aInit
, ErrorResult
& aRv
) {
357 return CreateAndInitializeAResponse(aGlobal
, aBody
, VoidCString(), aInit
,
361 already_AddRefed
<Response
> Response::Clone(JSContext
* aCx
, ErrorResult
& aRv
) {
362 bool bodyUsed
= BodyUsed();
364 if (!bodyUsed
&& mReadableStreamBody
) {
365 bool locked
= mReadableStreamBody
->Locked();
370 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
374 RefPtr
<FetchStreamReader
> streamReader
;
375 nsCOMPtr
<nsIInputStream
> inputStream
;
377 RefPtr
<ReadableStream
> body
;
378 MaybeTeeReadableStreamBody(aCx
, getter_AddRefs(body
),
379 getter_AddRefs(streamReader
),
380 getter_AddRefs(inputStream
), aRv
);
381 if (NS_WARN_IF(aRv
.Failed())) {
385 MOZ_ASSERT_IF(body
, streamReader
);
386 MOZ_ASSERT_IF(body
, inputStream
);
388 SafeRefPtr
<InternalResponse
> ir
=
389 mInternalResponse
->Clone(body
? InternalResponse::eDontCloneInputStream
390 : InternalResponse::eCloneInputStream
);
392 RefPtr
<Response
> response
=
393 new Response(mOwner
, ir
.clonePtr(), GetSignalImpl());
396 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
397 // if this body is a native stream. In this case the InternalResponse will
398 // have a clone of the native body and the ReadableStream will be created
400 response
->SetReadableStreamBody(aCx
, body
);
401 response
->mFetchStreamReader
= streamReader
;
402 ir
->SetBody(inputStream
, InternalResponse::UNKNOWN_BODY_SIZE
);
405 return response
.forget();
408 already_AddRefed
<Response
> Response::CloneUnfiltered(JSContext
* aCx
,
411 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
415 RefPtr
<FetchStreamReader
> streamReader
;
416 nsCOMPtr
<nsIInputStream
> inputStream
;
418 RefPtr
<ReadableStream
> body
;
419 MaybeTeeReadableStreamBody(aCx
, getter_AddRefs(body
),
420 getter_AddRefs(streamReader
),
421 getter_AddRefs(inputStream
), aRv
);
422 if (NS_WARN_IF(aRv
.Failed())) {
426 MOZ_ASSERT_IF(body
, streamReader
);
427 MOZ_ASSERT_IF(body
, inputStream
);
429 SafeRefPtr
<InternalResponse
> clone
=
430 mInternalResponse
->Clone(body
? InternalResponse::eDontCloneInputStream
431 : InternalResponse::eCloneInputStream
);
433 SafeRefPtr
<InternalResponse
> ir
= clone
->Unfiltered();
434 RefPtr
<Response
> ref
= new Response(mOwner
, ir
.clonePtr(), GetSignalImpl());
437 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
438 // if this body is a native stream. In this case the InternalResponse will
439 // have a clone of the native body and the ReadableStream will be created
441 ref
->SetReadableStreamBody(aCx
, body
);
442 ref
->mFetchStreamReader
= streamReader
;
443 ir
->SetBody(inputStream
, InternalResponse::UNKNOWN_BODY_SIZE
);
449 void Response::SetBody(nsIInputStream
* aBody
, int64_t aBodySize
) {
450 MOZ_ASSERT(!BodyUsed());
451 mInternalResponse
->SetBody(aBody
, aBodySize
);
454 SafeRefPtr
<InternalResponse
> Response::GetInternalResponse() const {
455 return mInternalResponse
.clonePtr();
458 Headers
* Response::Headers_() {
460 mHeaders
= new Headers(mOwner
, mInternalResponse
->Headers());
466 } // namespace mozilla::dom