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 #include "mozilla/dom/ReadableStreamDefaultReader.h"
33 namespace mozilla::dom
{
35 NS_IMPL_ADDREF_INHERITED(Response
, FetchBody
<Response
>)
36 NS_IMPL_RELEASE_INHERITED(Response
, FetchBody
<Response
>)
38 NS_IMPL_CYCLE_COLLECTION_CLASS(Response
)
40 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
41 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner
)
42 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders
)
43 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalImpl
)
44 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader
)
45 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadableStreamBody
)
46 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadableStreamReader
)
47 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner
)
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders
)
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalImpl
)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader
)
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadableStreamBody
)
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadableStreamReader
)
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
59 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Response
, FetchBody
<Response
>)
60 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
61 NS_IMPL_CYCLE_COLLECTION_TRACE_END
63 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response
)
64 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
65 NS_INTERFACE_MAP_END_INHERITING(FetchBody
<Response
>)
67 Response::Response(nsIGlobalObject
* aGlobal
,
68 SafeRefPtr
<InternalResponse
> aInternalResponse
,
69 AbortSignalImpl
* aSignalImpl
)
70 : FetchBody
<Response
>(aGlobal
),
71 mInternalResponse(std::move(aInternalResponse
)),
72 mSignalImpl(aSignalImpl
) {
74 mInternalResponse
->Headers()->Guard() == HeadersGuardEnum::Immutable
||
75 mInternalResponse
->Headers()->Guard() == HeadersGuardEnum::Response
);
77 mozilla::HoldJSObjects(this);
80 Response::~Response() { mozilla::DropJSObjects(this); }
83 already_AddRefed
<Response
> Response::Error(const GlobalObject
& aGlobal
) {
84 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
85 RefPtr
<Response
> r
= new Response(
86 global
, InternalResponse::NetworkError(NS_ERROR_FAILURE
), nullptr);
91 already_AddRefed
<Response
> Response::Redirect(const GlobalObject
& aGlobal
,
92 const nsAString
& aUrl
,
95 nsAutoString parsedURL
;
97 if (NS_IsMainThread()) {
98 nsIURI
* baseURI
= nullptr;
99 nsCOMPtr
<nsPIDOMWindowInner
> inner(
100 do_QueryInterface(aGlobal
.GetAsSupports()));
101 Document
* doc
= inner
? inner
->GetExtantDoc() : nullptr;
103 baseURI
= doc
->GetBaseURI();
105 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
107 if (!AppendUTF16toUTF8(aUrl
, url
, fallible
)) {
108 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
112 nsCOMPtr
<nsIURI
> resolvedURI
;
113 nsresult rv
= NS_NewURI(getter_AddRefs(resolvedURI
), url
, nullptr, baseURI
);
114 if (NS_WARN_IF(NS_FAILED(rv
))) {
115 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(url
);
120 rv
= resolvedURI
->GetSpec(spec
);
121 if (NS_WARN_IF(NS_FAILED(rv
))) {
122 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(url
);
126 CopyUTF8toUTF16(spec
, parsedURL
);
128 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
130 worker
->AssertIsOnWorkerThread();
132 NS_ConvertUTF8toUTF16
baseURL(worker
->GetLocationInfo().mHref
);
134 URL::Constructor(aGlobal
.GetAsSupports(), aUrl
, baseURL
, aRv
);
139 url
->GetHref(parsedURL
);
142 if (aStatus
!= 301 && aStatus
!= 302 && aStatus
!= 303 && aStatus
!= 307 &&
144 aRv
.ThrowRangeError("Invalid redirect status code.");
148 // We can't just pass nullptr for our null-valued Nullable, because the
149 // fetch::ResponseBodyInit is a non-temporary type due to the MOZ_RAII
150 // annotations on some of its members.
151 Nullable
<fetch::ResponseBodyInit
> body
;
153 init
.mStatus
= aStatus
;
154 init
.mStatusText
.AssignASCII("");
155 RefPtr
<Response
> r
= Response::Constructor(aGlobal
, body
, init
, aRv
);
156 if (NS_WARN_IF(aRv
.Failed())) {
160 r
->GetInternalHeaders()->Set("Location"_ns
, NS_ConvertUTF16toUTF8(parsedURL
),
162 if (NS_WARN_IF(aRv
.Failed())) {
165 r
->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable
, aRv
);
166 MOZ_ASSERT(!aRv
.Failed());
172 already_AddRefed
<Response
> Response::Constructor(
173 const GlobalObject
& aGlobal
, const Nullable
<fetch::ResponseBodyInit
>& aBody
,
174 const ResponseInit
& aInit
, ErrorResult
& aRv
) {
175 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
177 if (NS_WARN_IF(!global
)) {
178 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
182 if (aInit
.mStatus
< 200 || aInit
.mStatus
> 599) {
183 aRv
.ThrowRangeError("Invalid response status code.");
187 // Check if the status text contains illegal characters
188 nsACString::const_iterator start
, end
;
189 aInit
.mStatusText
.BeginReading(start
);
190 aInit
.mStatusText
.EndReading(end
);
191 if (FindCharInReadable('\r', start
, end
)) {
192 aRv
.ThrowTypeError
<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR
>();
195 // Reset iterator since FindCharInReadable advances it.
196 aInit
.mStatusText
.BeginReading(start
);
197 if (FindCharInReadable('\n', start
, end
)) {
198 aRv
.ThrowTypeError
<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR
>();
202 SafeRefPtr
<InternalResponse
> internalResponse
=
203 MakeSafeRefPtr
<InternalResponse
>(aInit
.mStatus
, aInit
.mStatusText
);
205 UniquePtr
<mozilla::ipc::PrincipalInfo
> principalInfo
;
207 // Grab a valid channel info from the global so this response is 'valid' for
209 if (NS_IsMainThread()) {
211 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
);
213 Document
* doc
= window
->GetExtantDoc();
215 info
.InitFromDocument(doc
);
217 principalInfo
.reset(new mozilla::ipc::PrincipalInfo());
219 PrincipalToPrincipalInfo(doc
->NodePrincipal(), principalInfo
.get());
220 if (NS_WARN_IF(NS_FAILED(rv
))) {
221 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
225 internalResponse
->InitChannelInfo(info
);
226 } else if (global
->PrincipalOrNull()->IsSystemPrincipal()) {
227 info
.InitFromChromeGlobal(global
);
229 internalResponse
->InitChannelInfo(info
);
233 * The channel info is left uninitialized if neither the above `if` nor
234 * `else if` statements are executed; this could be because we're in a
235 * WebExtensions content script, where the global (i.e. `global`) is a
236 * wrapper, and the principal is an expanded principal. In this case,
237 * as far as I can tell, there's no way to get the security info, but we'd
238 * like the `Response` to be successfully constructed.
241 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
243 internalResponse
->InitChannelInfo(worker
->GetChannelInfo());
245 MakeUnique
<mozilla::ipc::PrincipalInfo
>(worker
->GetPrincipalInfo());
248 internalResponse
->SetPrincipalInfo(std::move(principalInfo
));
251 new Response(global
, internalResponse
.clonePtr(), nullptr);
253 if (aInit
.mHeaders
.WasPassed()) {
254 internalResponse
->Headers()->Clear();
256 // Instead of using Fill, create an object to allow the constructor to
257 // unwrap the HeadersInit.
258 RefPtr
<Headers
> headers
=
259 Headers::Create(global
, aInit
.mHeaders
.Value(), aRv
);
264 internalResponse
->Headers()->Fill(*headers
->GetInternalHeaders(), aRv
);
265 if (NS_WARN_IF(aRv
.Failed())) {
270 if (!aBody
.IsNull()) {
271 if (aInit
.mStatus
== 204 || aInit
.mStatus
== 205 || aInit
.mStatus
== 304) {
272 aRv
.ThrowTypeError("Response body is given with a null body status.");
276 nsCString contentTypeWithCharset
;
277 nsCOMPtr
<nsIInputStream
> bodyStream
;
278 int64_t bodySize
= InternalResponse::UNKNOWN_BODY_SIZE
;
280 const fetch::ResponseBodyInit
& body
= aBody
.Value();
281 if (body
.IsReadableStream()) {
282 JSContext
* cx
= aGlobal
.Context();
283 aRv
.MightThrowJSException();
285 ReadableStream
& readableStream
= body
.GetAsReadableStream();
287 if (readableStream
.Locked() || readableStream
.Disturbed()) {
288 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
292 r
->SetReadableStreamBody(cx
, &readableStream
);
294 // If this is a DOM generated ReadableStream, we can extract the
295 // inputStream directly.
296 if (readableStream
.HasNativeUnderlyingSource()) {
297 BodyStreamHolder
* underlyingSource
=
298 readableStream
.GetNativeUnderlyingSource();
299 MOZ_ASSERT(underlyingSource
);
301 aRv
= BodyStream::RetrieveInputStream(underlyingSource
,
302 getter_AddRefs(bodyStream
));
304 if (NS_WARN_IF(aRv
.Failed())) {
308 // If this is a JS-created ReadableStream, let's create a
309 // FetchStreamReader.
310 aRv
= FetchStreamReader::Create(aGlobal
.Context(), global
,
311 getter_AddRefs(r
->mFetchStreamReader
),
312 getter_AddRefs(bodyStream
));
313 if (NS_WARN_IF(aRv
.Failed())) {
319 aRv
= ExtractByteStreamFromBody(body
, getter_AddRefs(bodyStream
),
320 contentTypeWithCharset
, size
);
321 if (NS_WARN_IF(aRv
.Failed())) {
328 internalResponse
->SetBody(bodyStream
, bodySize
);
330 if (!contentTypeWithCharset
.IsVoid() &&
331 !internalResponse
->Headers()->Has("Content-Type"_ns
, aRv
)) {
332 // Ignore Append() failing here.
334 internalResponse
->Headers()->Append("Content-Type"_ns
,
335 contentTypeWithCharset
, error
);
336 error
.SuppressException();
347 already_AddRefed
<Response
> Response::Clone(JSContext
* aCx
, ErrorResult
& aRv
) {
348 bool bodyUsed
= GetBodyUsed(aRv
);
349 if (NS_WARN_IF(aRv
.Failed())) {
353 if (!bodyUsed
&& mReadableStreamBody
) {
354 bool locked
= mReadableStreamBody
->Locked();
359 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
363 RefPtr
<FetchStreamReader
> streamReader
;
364 nsCOMPtr
<nsIInputStream
> inputStream
;
366 RefPtr
<ReadableStream
> body
;
367 MaybeTeeReadableStreamBody(aCx
, getter_AddRefs(body
),
368 getter_AddRefs(streamReader
),
369 getter_AddRefs(inputStream
), aRv
);
370 if (NS_WARN_IF(aRv
.Failed())) {
374 MOZ_ASSERT_IF(body
, streamReader
);
375 MOZ_ASSERT_IF(body
, inputStream
);
377 SafeRefPtr
<InternalResponse
> ir
=
378 mInternalResponse
->Clone(body
? InternalResponse::eDontCloneInputStream
379 : InternalResponse::eCloneInputStream
);
381 RefPtr
<Response
> response
=
382 new Response(mOwner
, ir
.clonePtr(), GetSignalImpl());
385 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
386 // if this body is a native stream. In this case the InternalResponse will
387 // have a clone of the native body and the ReadableStream will be created
389 response
->SetReadableStreamBody(aCx
, body
);
390 response
->mFetchStreamReader
= streamReader
;
391 ir
->SetBody(inputStream
, InternalResponse::UNKNOWN_BODY_SIZE
);
394 return response
.forget();
397 already_AddRefed
<Response
> Response::CloneUnfiltered(JSContext
* aCx
,
399 if (GetBodyUsed(aRv
)) {
400 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
404 RefPtr
<FetchStreamReader
> streamReader
;
405 nsCOMPtr
<nsIInputStream
> inputStream
;
407 RefPtr
<ReadableStream
> body
;
408 MaybeTeeReadableStreamBody(aCx
, getter_AddRefs(body
),
409 getter_AddRefs(streamReader
),
410 getter_AddRefs(inputStream
), aRv
);
411 if (NS_WARN_IF(aRv
.Failed())) {
415 MOZ_ASSERT_IF(body
, streamReader
);
416 MOZ_ASSERT_IF(body
, inputStream
);
418 SafeRefPtr
<InternalResponse
> clone
=
419 mInternalResponse
->Clone(body
? InternalResponse::eDontCloneInputStream
420 : InternalResponse::eCloneInputStream
);
422 SafeRefPtr
<InternalResponse
> ir
= clone
->Unfiltered();
423 RefPtr
<Response
> ref
= new Response(mOwner
, ir
.clonePtr(), GetSignalImpl());
426 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
427 // if this body is a native stream. In this case the InternalResponse will
428 // have a clone of the native body and the ReadableStream will be created
430 ref
->SetReadableStreamBody(aCx
, body
);
431 ref
->mFetchStreamReader
= streamReader
;
432 ir
->SetBody(inputStream
, InternalResponse::UNKNOWN_BODY_SIZE
);
438 void Response::SetBody(nsIInputStream
* aBody
, int64_t aBodySize
) {
439 MOZ_ASSERT(!CheckBodyUsed());
440 mInternalResponse
->SetBody(aBody
, aBodySize
);
443 SafeRefPtr
<InternalResponse
> Response::GetInternalResponse() const {
444 return mInternalResponse
.clonePtr();
447 Headers
* Response::Headers_() {
449 mHeaders
= new Headers(mOwner
, mInternalResponse
->Headers());
455 } // namespace mozilla::dom