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/BodyStream.h"
17 #include "mozilla/dom/Document.h"
18 #include "mozilla/dom/FetchBinding.h"
19 #include "mozilla/dom/ResponseBinding.h"
20 #include "mozilla/dom/Headers.h"
21 #include "mozilla/dom/Promise.h"
22 #include "mozilla/dom/URL.h"
23 #include "mozilla/dom/WorkerPrivate.h"
25 #include "nsDOMString.h"
27 #include "BodyExtractor.h"
28 #include "FetchStreamReader.h"
29 #include "InternalResponse.h"
31 namespace mozilla::dom
{
33 NS_IMPL_ADDREF_INHERITED(Response
, FetchBody
<Response
>)
34 NS_IMPL_RELEASE_INHERITED(Response
, FetchBody
<Response
>)
36 NS_IMPL_CYCLE_COLLECTION_CLASS(Response
)
38 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
39 AbortFollower::Unlink(static_cast<AbortFollower
*>(tmp
));
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
)
45 tmp
->mReadableStreamBody
= nullptr;
46 tmp
->mReadableStreamReader
= nullptr;
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
52 AbortFollower::Traverse(static_cast<AbortFollower
*>(tmp
), cb
);
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner
)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders
)
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalImpl
)
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader
)
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
59 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
60 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamBody
)
61 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader
)
62 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
63 NS_IMPL_CYCLE_COLLECTION_TRACE_END
65 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response
)
66 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
67 NS_INTERFACE_MAP_END_INHERITING(FetchBody
<Response
>)
69 Response::Response(nsIGlobalObject
* aGlobal
,
70 InternalResponse
* aInternalResponse
,
71 AbortSignalImpl
* aSignalImpl
)
72 : FetchBody
<Response
>(aGlobal
),
73 mInternalResponse(aInternalResponse
),
74 mSignalImpl(aSignalImpl
) {
76 aInternalResponse
->Headers()->Guard() == HeadersGuardEnum::Immutable
||
77 aInternalResponse
->Headers()->Guard() == HeadersGuardEnum::Response
);
79 mozilla::HoldJSObjects(this);
82 Response::~Response() { mozilla::DropJSObjects(this); }
85 already_AddRefed
<Response
> Response::Error(const GlobalObject
& aGlobal
) {
86 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
87 RefPtr
<InternalResponse
> error
=
88 InternalResponse::NetworkError(NS_ERROR_FAILURE
);
89 RefPtr
<Response
> r
= new Response(global
, error
, nullptr);
94 already_AddRefed
<Response
> Response::Redirect(const GlobalObject
& aGlobal
,
95 const nsAString
& aUrl
,
98 nsAutoString parsedURL
;
100 if (NS_IsMainThread()) {
101 nsIURI
* baseURI
= nullptr;
102 nsCOMPtr
<nsPIDOMWindowInner
> inner(
103 do_QueryInterface(aGlobal
.GetAsSupports()));
104 Document
* doc
= inner
? inner
->GetExtantDoc() : nullptr;
106 baseURI
= doc
->GetBaseURI();
108 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
110 if (!AppendUTF16toUTF8(aUrl
, url
, fallible
)) {
111 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
115 nsCOMPtr
<nsIURI
> resolvedURI
;
116 nsresult rv
= NS_NewURI(getter_AddRefs(resolvedURI
), url
, nullptr, baseURI
);
117 if (NS_WARN_IF(NS_FAILED(rv
))) {
118 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(url
);
123 rv
= resolvedURI
->GetSpec(spec
);
124 if (NS_WARN_IF(NS_FAILED(rv
))) {
125 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(url
);
129 CopyUTF8toUTF16(spec
, parsedURL
);
131 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
133 worker
->AssertIsOnWorkerThread();
135 NS_ConvertUTF8toUTF16
baseURL(worker
->GetLocationInfo().mHref
);
137 URL::Constructor(aGlobal
.GetAsSupports(), aUrl
, baseURL
, aRv
);
142 url
->GetHref(parsedURL
);
145 if (aStatus
!= 301 && aStatus
!= 302 && aStatus
!= 303 && aStatus
!= 307 &&
147 aRv
.ThrowRangeError("Invalid redirect status code.");
151 // We can't just pass nullptr for our null-valued Nullable, because the
152 // fetch::ResponseBodyInit is a non-temporary type due to the MOZ_RAII
153 // annotations on some of its members.
154 Nullable
<fetch::ResponseBodyInit
> body
;
156 init
.mStatus
= aStatus
;
157 init
.mStatusText
.AssignASCII("");
158 RefPtr
<Response
> r
= Response::Constructor(aGlobal
, body
, init
, aRv
);
159 if (NS_WARN_IF(aRv
.Failed())) {
163 r
->GetInternalHeaders()->Set("Location"_ns
, NS_ConvertUTF16toUTF8(parsedURL
),
165 if (NS_WARN_IF(aRv
.Failed())) {
168 r
->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable
, aRv
);
169 MOZ_ASSERT(!aRv
.Failed());
175 already_AddRefed
<Response
> Response::Constructor(
176 const GlobalObject
& aGlobal
, const Nullable
<fetch::ResponseBodyInit
>& aBody
,
177 const ResponseInit
& aInit
, ErrorResult
& aRv
) {
178 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
180 if (NS_WARN_IF(!global
)) {
181 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
185 if (aInit
.mStatus
< 200 || aInit
.mStatus
> 599) {
186 aRv
.ThrowRangeError("Invalid response status code.");
190 // Check if the status text contains illegal characters
191 nsACString::const_iterator start
, end
;
192 aInit
.mStatusText
.BeginReading(start
);
193 aInit
.mStatusText
.EndReading(end
);
194 if (FindCharInReadable('\r', start
, end
)) {
195 aRv
.ThrowTypeError
<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR
>();
198 // Reset iterator since FindCharInReadable advances it.
199 aInit
.mStatusText
.BeginReading(start
);
200 if (FindCharInReadable('\n', start
, end
)) {
201 aRv
.ThrowTypeError
<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR
>();
205 RefPtr
<InternalResponse
> internalResponse
=
206 new InternalResponse(aInit
.mStatus
, aInit
.mStatusText
);
208 UniquePtr
<mozilla::ipc::PrincipalInfo
> principalInfo
;
210 // Grab a valid channel info from the global so this response is 'valid' for
212 if (NS_IsMainThread()) {
214 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
);
216 Document
* doc
= window
->GetExtantDoc();
218 info
.InitFromDocument(doc
);
220 principalInfo
.reset(new mozilla::ipc::PrincipalInfo());
222 PrincipalToPrincipalInfo(doc
->NodePrincipal(), principalInfo
.get());
223 if (NS_WARN_IF(NS_FAILED(rv
))) {
224 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
228 internalResponse
->InitChannelInfo(info
);
229 } else if (global
->PrincipalOrNull()->IsSystemPrincipal()) {
230 info
.InitFromChromeGlobal(global
);
232 internalResponse
->InitChannelInfo(info
);
236 * The channel info is left uninitialized if neither the above `if` nor
237 * `else if` statements are executed; this could be because we're in a
238 * WebExtensions content script, where the global (i.e. `global`) is a
239 * wrapper, and the principal is an expanded principal. In this case,
240 * as far as I can tell, there's no way to get the security info, but we'd
241 * like the `Response` to be successfully constructed.
244 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
246 internalResponse
->InitChannelInfo(worker
->GetChannelInfo());
248 MakeUnique
<mozilla::ipc::PrincipalInfo
>(worker
->GetPrincipalInfo());
251 internalResponse
->SetPrincipalInfo(std::move(principalInfo
));
253 RefPtr
<Response
> r
= new Response(global
, internalResponse
, nullptr);
255 if (aInit
.mHeaders
.WasPassed()) {
256 internalResponse
->Headers()->Clear();
258 // Instead of using Fill, create an object to allow the constructor to
259 // unwrap the HeadersInit.
260 RefPtr
<Headers
> headers
=
261 Headers::Create(global
, aInit
.mHeaders
.Value(), aRv
);
266 internalResponse
->Headers()->Fill(*headers
->GetInternalHeaders(), aRv
);
267 if (NS_WARN_IF(aRv
.Failed())) {
272 if (!aBody
.IsNull()) {
273 if (aInit
.mStatus
== 204 || aInit
.mStatus
== 205 || aInit
.mStatus
== 304) {
274 aRv
.ThrowTypeError("Response body is given with a null body status.");
278 nsCString contentTypeWithCharset
;
279 nsCOMPtr
<nsIInputStream
> bodyStream
;
280 int64_t bodySize
= InternalResponse::UNKNOWN_BODY_SIZE
;
282 const fetch::ResponseBodyInit
& body
= aBody
.Value();
283 if (body
.IsReadableStream()) {
284 aRv
.MightThrowJSException();
286 JSContext
* cx
= aGlobal
.Context();
287 const ReadableStream
& readableStream
= body
.GetAsReadableStream();
289 JS::Rooted
<JSObject
*> readableStreamObj(cx
, readableStream
.Obj());
293 if (!JS::ReadableStreamIsDisturbed(cx
, readableStreamObj
, &disturbed
) ||
294 !JS::ReadableStreamIsLocked(cx
, readableStreamObj
, &locked
)) {
295 aRv
.StealExceptionFromJSContext(cx
);
298 if (disturbed
|| locked
) {
299 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
303 r
->SetReadableStreamBody(cx
, readableStreamObj
);
305 JS::ReadableStreamMode streamMode
;
306 if (!JS::ReadableStreamGetMode(cx
, readableStreamObj
, &streamMode
)) {
307 aRv
.StealExceptionFromJSContext(cx
);
310 if (streamMode
== JS::ReadableStreamMode::ExternalSource
) {
311 // If this is a DOM generated ReadableStream, we can extract the
312 // inputStream directly.
313 JS::ReadableStreamUnderlyingSource
* underlyingSource
= nullptr;
314 if (!JS::ReadableStreamGetExternalUnderlyingSource(
315 cx
, readableStreamObj
, &underlyingSource
)) {
316 aRv
.StealExceptionFromJSContext(cx
);
320 MOZ_ASSERT(underlyingSource
);
322 aRv
= BodyStream::RetrieveInputStream(underlyingSource
,
323 getter_AddRefs(bodyStream
));
325 // The releasing of the external source is needed in order to avoid an
326 // extra stream lock.
327 if (!JS::ReadableStreamReleaseExternalUnderlyingSource(
328 cx
, readableStreamObj
)) {
329 aRv
.StealExceptionFromJSContext(cx
);
332 if (NS_WARN_IF(aRv
.Failed())) {
336 // If this is a JS-created ReadableStream, let's create a
337 // FetchStreamReader.
338 aRv
= FetchStreamReader::Create(aGlobal
.Context(), global
,
339 getter_AddRefs(r
->mFetchStreamReader
),
340 getter_AddRefs(bodyStream
));
341 if (NS_WARN_IF(aRv
.Failed())) {
347 aRv
= ExtractByteStreamFromBody(body
, getter_AddRefs(bodyStream
),
348 contentTypeWithCharset
, size
);
349 if (NS_WARN_IF(aRv
.Failed())) {
356 internalResponse
->SetBody(bodyStream
, bodySize
);
358 if (!contentTypeWithCharset
.IsVoid() &&
359 !internalResponse
->Headers()->Has("Content-Type"_ns
, aRv
)) {
360 // Ignore Append() failing here.
362 internalResponse
->Headers()->Append("Content-Type"_ns
,
363 contentTypeWithCharset
, error
);
364 error
.SuppressException();
375 already_AddRefed
<Response
> Response::Clone(JSContext
* aCx
, ErrorResult
& aRv
) {
376 bool bodyUsed
= GetBodyUsed(aRv
);
377 if (NS_WARN_IF(aRv
.Failed())) {
381 if (!bodyUsed
&& mReadableStreamBody
) {
382 aRv
.MightThrowJSException();
385 if (!jsapi
.Init(mOwner
)) {
386 aRv
.Throw(NS_ERROR_FAILURE
);
390 JSContext
* cx
= jsapi
.cx();
391 JS::Rooted
<JSObject
*> body(cx
, mReadableStreamBody
);
393 // We just need to check the 'locked' state because GetBodyUsed() already
394 // checked the 'disturbed' state.
395 if (!JS::ReadableStreamIsLocked(cx
, body
, &locked
)) {
396 aRv
.StealExceptionFromJSContext(cx
);
404 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
408 RefPtr
<FetchStreamReader
> streamReader
;
409 nsCOMPtr
<nsIInputStream
> inputStream
;
411 JS::Rooted
<JSObject
*> body(aCx
);
412 MaybeTeeReadableStreamBody(aCx
, &body
, getter_AddRefs(streamReader
),
413 getter_AddRefs(inputStream
), aRv
);
414 if (NS_WARN_IF(aRv
.Failed())) {
418 MOZ_ASSERT_IF(body
, streamReader
);
419 MOZ_ASSERT_IF(body
, inputStream
);
421 RefPtr
<InternalResponse
> ir
=
422 mInternalResponse
->Clone(body
? InternalResponse::eDontCloneInputStream
423 : InternalResponse::eCloneInputStream
);
425 RefPtr
<Response
> response
= new Response(mOwner
, ir
, GetSignalImpl());
428 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
429 // if this body is a native stream. In this case the InternalResponse will
430 // have a clone of the native body and the ReadableStream will be created
432 response
->SetReadableStreamBody(aCx
, body
);
433 response
->mFetchStreamReader
= streamReader
;
434 ir
->SetBody(inputStream
, InternalResponse::UNKNOWN_BODY_SIZE
);
437 return response
.forget();
440 already_AddRefed
<Response
> Response::CloneUnfiltered(JSContext
* aCx
,
442 if (GetBodyUsed(aRv
)) {
443 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
447 RefPtr
<FetchStreamReader
> streamReader
;
448 nsCOMPtr
<nsIInputStream
> inputStream
;
450 JS::Rooted
<JSObject
*> body(aCx
);
451 MaybeTeeReadableStreamBody(aCx
, &body
, getter_AddRefs(streamReader
),
452 getter_AddRefs(inputStream
), aRv
);
453 if (NS_WARN_IF(aRv
.Failed())) {
457 MOZ_ASSERT_IF(body
, streamReader
);
458 MOZ_ASSERT_IF(body
, inputStream
);
460 RefPtr
<InternalResponse
> clone
=
461 mInternalResponse
->Clone(body
? InternalResponse::eDontCloneInputStream
462 : InternalResponse::eCloneInputStream
);
464 RefPtr
<InternalResponse
> ir
= clone
->Unfiltered();
465 RefPtr
<Response
> ref
= new Response(mOwner
, ir
, GetSignalImpl());
468 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
469 // if this body is a native stream. In this case the InternalResponse will
470 // have a clone of the native body and the ReadableStream will be created
472 ref
->SetReadableStreamBody(aCx
, body
);
473 ref
->mFetchStreamReader
= streamReader
;
474 ir
->SetBody(inputStream
, InternalResponse::UNKNOWN_BODY_SIZE
);
480 void Response::SetBody(nsIInputStream
* aBody
, int64_t aBodySize
) {
481 MOZ_ASSERT(!CheckBodyUsed());
482 mInternalResponse
->SetBody(aBody
, aBodySize
);
485 already_AddRefed
<InternalResponse
> Response::GetInternalResponse() const {
486 RefPtr
<InternalResponse
> ref
= mInternalResponse
;
490 Headers
* Response::Headers_() {
492 mHeaders
= new Headers(mOwner
, mInternalResponse
->Headers());
498 } // namespace mozilla::dom