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 nsAString
& aUrl
,
90 nsAutoString parsedURL
;
92 if (NS_IsMainThread()) {
93 nsIURI
* baseURI
= nullptr;
94 nsCOMPtr
<nsPIDOMWindowInner
> inner(
95 do_QueryInterface(aGlobal
.GetAsSupports()));
96 Document
* doc
= inner
? inner
->GetExtantDoc() : nullptr;
98 baseURI
= doc
->GetBaseURI();
100 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
102 if (!AppendUTF16toUTF8(aUrl
, url
, fallible
)) {
103 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
107 nsCOMPtr
<nsIURI
> resolvedURI
;
108 nsresult rv
= NS_NewURI(getter_AddRefs(resolvedURI
), url
, nullptr, baseURI
);
109 if (NS_WARN_IF(NS_FAILED(rv
))) {
110 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(url
);
115 rv
= resolvedURI
->GetSpec(spec
);
116 if (NS_WARN_IF(NS_FAILED(rv
))) {
117 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(url
);
121 CopyUTF8toUTF16(spec
, parsedURL
);
123 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
125 worker
->AssertIsOnWorkerThread();
127 NS_ConvertUTF8toUTF16
baseURL(worker
->GetLocationInfo().mHref
);
129 URL::Constructor(aGlobal
.GetAsSupports(), aUrl
, baseURL
, aRv
);
134 url
->GetHref(parsedURL
);
137 if (aStatus
!= 301 && aStatus
!= 302 && aStatus
!= 303 && aStatus
!= 307 &&
139 aRv
.ThrowRangeError("Invalid redirect status code.");
143 // We can't just pass nullptr for our null-valued Nullable, because the
144 // fetch::ResponseBodyInit is a non-temporary type due to the MOZ_RAII
145 // annotations on some of its members.
146 Nullable
<fetch::ResponseBodyInit
> body
;
148 init
.mStatus
= aStatus
;
149 init
.mStatusText
.AssignASCII("");
150 RefPtr
<Response
> r
= Response::Constructor(aGlobal
, body
, init
, aRv
);
151 if (NS_WARN_IF(aRv
.Failed())) {
155 r
->GetInternalHeaders()->Set("Location"_ns
, NS_ConvertUTF16toUTF8(parsedURL
),
157 if (NS_WARN_IF(aRv
.Failed())) {
160 r
->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable
, aRv
);
161 MOZ_ASSERT(!aRv
.Failed());
166 /* static */ already_AddRefed
<Response
> Response::CreateAndInitializeAResponse(
167 const GlobalObject
& aGlobal
, const Nullable
<fetch::ResponseBodyInit
>& aBody
,
168 const nsACString
& aDefaultContentType
, const ResponseInit
& aInit
,
170 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
172 if (NS_WARN_IF(!global
)) {
173 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
177 // Initialize a response, Step 1.
178 if (aInit
.mStatus
< 200 || aInit
.mStatus
> 599) {
179 aRv
.ThrowRangeError("Invalid response status code.");
183 // Initialize a response, Step 2: Check if the status text contains illegal
185 nsACString::const_iterator start
, end
;
186 aInit
.mStatusText
.BeginReading(start
);
187 aInit
.mStatusText
.EndReading(end
);
188 if (FindCharInReadable('\r', start
, end
)) {
189 aRv
.ThrowTypeError
<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR
>();
192 // Reset iterator since FindCharInReadable advances it.
193 aInit
.mStatusText
.BeginReading(start
);
194 if (FindCharInReadable('\n', start
, end
)) {
195 aRv
.ThrowTypeError
<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR
>();
199 // Initialize a response, Step 3-4.
200 SafeRefPtr
<InternalResponse
> internalResponse
=
201 MakeSafeRefPtr
<InternalResponse
>(aInit
.mStatus
, aInit
.mStatusText
);
203 UniquePtr
<mozilla::ipc::PrincipalInfo
> principalInfo
;
205 // Grab a valid channel info from the global so this response is 'valid' for
207 if (NS_IsMainThread()) {
209 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
);
211 Document
* doc
= window
->GetExtantDoc();
213 info
.InitFromDocument(doc
);
215 principalInfo
.reset(new mozilla::ipc::PrincipalInfo());
217 PrincipalToPrincipalInfo(doc
->NodePrincipal(), principalInfo
.get());
218 if (NS_WARN_IF(NS_FAILED(rv
))) {
219 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
223 internalResponse
->InitChannelInfo(info
);
224 } else if (global
->PrincipalOrNull()->IsSystemPrincipal()) {
225 info
.InitFromChromeGlobal(global
);
227 internalResponse
->InitChannelInfo(info
);
231 * The channel info is left uninitialized if neither the above `if` nor
232 * `else if` statements are executed; this could be because we're in a
233 * WebExtensions content script, where the global (i.e. `global`) is a
234 * wrapper, and the principal is an expanded principal. In this case,
235 * as far as I can tell, there's no way to get the security info, but we'd
236 * like the `Response` to be successfully constructed.
239 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
241 internalResponse
->InitChannelInfo(worker
->GetChannelInfo());
243 MakeUnique
<mozilla::ipc::PrincipalInfo
>(worker
->GetPrincipalInfo());
246 internalResponse
->SetPrincipalInfo(std::move(principalInfo
));
249 new Response(global
, internalResponse
.clonePtr(), nullptr);
251 if (aInit
.mHeaders
.WasPassed()) {
252 internalResponse
->Headers()->Clear();
254 // Instead of using Fill, create an object to allow the constructor to
255 // unwrap the HeadersInit.
256 RefPtr
<Headers
> headers
=
257 Headers::Create(global
, aInit
.mHeaders
.Value(), aRv
);
262 internalResponse
->Headers()->Fill(*headers
->GetInternalHeaders(), aRv
);
263 if (NS_WARN_IF(aRv
.Failed())) {
268 if (!aBody
.IsNull()) {
269 if (aInit
.mStatus
== 204 || aInit
.mStatus
== 205 || aInit
.mStatus
== 304) {
270 aRv
.ThrowTypeError("Response body is given with a null body status.");
274 nsCString contentTypeWithCharset
;
275 contentTypeWithCharset
.SetIsVoid(true);
276 nsCOMPtr
<nsIInputStream
> bodyStream
;
277 int64_t bodySize
= InternalResponse::UNKNOWN_BODY_SIZE
;
279 const fetch::ResponseBodyInit
& body
= aBody
.Value();
280 if (body
.IsReadableStream()) {
281 JSContext
* cx
= aGlobal
.Context();
282 aRv
.MightThrowJSException();
284 ReadableStream
& readableStream
= body
.GetAsReadableStream();
286 if (readableStream
.Locked() || readableStream
.Disturbed()) {
287 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
291 r
->SetReadableStreamBody(cx
, &readableStream
);
293 // If this is a DOM generated ReadableStream, we can extract the
294 // inputStream directly.
295 if (nsIInputStream
* underlyingSource
=
296 readableStream
.MaybeGetInputStreamIfUnread()) {
297 bodyStream
= underlyingSource
;
299 // If this is a JS-created ReadableStream, let's create a
300 // FetchStreamReader.
301 aRv
= FetchStreamReader::Create(aGlobal
.Context(), global
,
302 getter_AddRefs(r
->mFetchStreamReader
),
303 getter_AddRefs(bodyStream
));
304 if (NS_WARN_IF(aRv
.Failed())) {
310 aRv
= ExtractByteStreamFromBody(body
, getter_AddRefs(bodyStream
),
311 contentTypeWithCharset
, size
);
312 if (NS_WARN_IF(aRv
.Failed())) {
316 if (!aDefaultContentType
.IsVoid()) {
317 contentTypeWithCharset
= aDefaultContentType
;
323 internalResponse
->SetBody(bodyStream
, bodySize
);
325 if (!contentTypeWithCharset
.IsVoid() &&
326 !internalResponse
->Headers()->Has("Content-Type"_ns
, aRv
)) {
327 // Ignore Append() failing here.
329 internalResponse
->Headers()->Append("Content-Type"_ns
,
330 contentTypeWithCharset
, error
);
331 error
.SuppressException();
343 already_AddRefed
<Response
> Response::CreateFromJson(const GlobalObject
& aGlobal
,
345 JS::Handle
<JS::Value
> aData
,
346 const ResponseInit
& aInit
,
348 aRv
.MightThrowJSException();
349 nsAutoString serializedValue
;
350 if (!nsContentUtils::StringifyJSON(aCx
, aData
, serializedValue
,
351 UndefinedIsVoidString
)) {
352 aRv
.StealExceptionFromJSContext(aCx
);
355 if (serializedValue
.IsVoid()) {
356 aRv
.ThrowTypeError
<MSG_JSON_INVALID_VALUE
>();
359 Nullable
<fetch::ResponseBodyInit
> body
;
360 body
.SetValue().SetAsUSVString().ShareOrDependUpon(serializedValue
);
361 return CreateAndInitializeAResponse(aGlobal
, body
, "application/json"_ns
,
366 already_AddRefed
<Response
> Response::Constructor(
367 const GlobalObject
& aGlobal
, const Nullable
<fetch::ResponseBodyInit
>& aBody
,
368 const ResponseInit
& aInit
, ErrorResult
& aRv
) {
369 return CreateAndInitializeAResponse(aGlobal
, aBody
, VoidCString(), aInit
,
373 already_AddRefed
<Response
> Response::Clone(JSContext
* aCx
, ErrorResult
& aRv
) {
374 bool bodyUsed
= BodyUsed();
376 if (!bodyUsed
&& mReadableStreamBody
) {
377 bool locked
= mReadableStreamBody
->Locked();
382 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
386 RefPtr
<FetchStreamReader
> streamReader
;
387 nsCOMPtr
<nsIInputStream
> inputStream
;
389 RefPtr
<ReadableStream
> body
;
390 MaybeTeeReadableStreamBody(aCx
, getter_AddRefs(body
),
391 getter_AddRefs(streamReader
),
392 getter_AddRefs(inputStream
), aRv
);
393 if (NS_WARN_IF(aRv
.Failed())) {
397 MOZ_ASSERT_IF(body
, streamReader
);
398 MOZ_ASSERT_IF(body
, inputStream
);
400 SafeRefPtr
<InternalResponse
> ir
=
401 mInternalResponse
->Clone(body
? InternalResponse::eDontCloneInputStream
402 : InternalResponse::eCloneInputStream
);
404 RefPtr
<Response
> response
=
405 new Response(mOwner
, ir
.clonePtr(), GetSignalImpl());
408 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
409 // if this body is a native stream. In this case the InternalResponse will
410 // have a clone of the native body and the ReadableStream will be created
412 response
->SetReadableStreamBody(aCx
, body
);
413 response
->mFetchStreamReader
= streamReader
;
414 ir
->SetBody(inputStream
, InternalResponse::UNKNOWN_BODY_SIZE
);
417 return response
.forget();
420 already_AddRefed
<Response
> Response::CloneUnfiltered(JSContext
* aCx
,
423 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
427 RefPtr
<FetchStreamReader
> streamReader
;
428 nsCOMPtr
<nsIInputStream
> inputStream
;
430 RefPtr
<ReadableStream
> body
;
431 MaybeTeeReadableStreamBody(aCx
, getter_AddRefs(body
),
432 getter_AddRefs(streamReader
),
433 getter_AddRefs(inputStream
), aRv
);
434 if (NS_WARN_IF(aRv
.Failed())) {
438 MOZ_ASSERT_IF(body
, streamReader
);
439 MOZ_ASSERT_IF(body
, inputStream
);
441 SafeRefPtr
<InternalResponse
> clone
=
442 mInternalResponse
->Clone(body
? InternalResponse::eDontCloneInputStream
443 : InternalResponse::eCloneInputStream
);
445 SafeRefPtr
<InternalResponse
> ir
= clone
->Unfiltered();
446 RefPtr
<Response
> ref
= new Response(mOwner
, ir
.clonePtr(), GetSignalImpl());
449 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
450 // if this body is a native stream. In this case the InternalResponse will
451 // have a clone of the native body and the ReadableStream will be created
453 ref
->SetReadableStreamBody(aCx
, body
);
454 ref
->mFetchStreamReader
= streamReader
;
455 ir
->SetBody(inputStream
, InternalResponse::UNKNOWN_BODY_SIZE
);
461 void Response::SetBody(nsIInputStream
* aBody
, int64_t aBodySize
) {
462 MOZ_ASSERT(!BodyUsed());
463 mInternalResponse
->SetBody(aBody
, aBodySize
);
466 SafeRefPtr
<InternalResponse
> Response::GetInternalResponse() const {
467 return mInternalResponse
.clonePtr();
470 Headers
* Response::Headers_() {
472 mHeaders
= new Headers(mOwner
, mInternalResponse
->Headers());
478 } // namespace mozilla::dom