Bug 1909121 - [wpt-sync] Update web-platform-tests to 5af3e9c2a2aba76ade00f0dbc3486e5...
[gecko.git] / dom / fetch / Response.cpp
blobbe3decc5143c79d51a2366428444d48ced4748cf
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/. */
7 #include "Response.h"
9 #include "nsISupportsImpl.h"
10 #include "nsIURI.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) {
68 MOZ_ASSERT(
69 mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
70 mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
72 mozilla::HoldJSObjects(this);
75 Response::~Response() { mozilla::DropJSObjects(this); }
77 /* static */
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);
82 return r.forget();
85 /* static */
86 already_AddRefed<Response> Response::Redirect(const GlobalObject& aGlobal,
87 const nsACString& aUrl,
88 uint16_t aStatus,
89 ErrorResult& aRv) {
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;
100 nsresult rv =
101 NS_NewURI(getter_AddRefs(resolvedURI), aUrl, nullptr, baseURI);
102 if (NS_WARN_IF(NS_FAILED(rv))) {
103 aRv.ThrowTypeError<MSG_INVALID_URL>(aUrl);
104 return nullptr;
107 rv = resolvedURI->GetSpec(parsedURL);
108 if (NS_WARN_IF(NS_FAILED(rv))) {
109 aRv.ThrowTypeError<MSG_INVALID_URL>(aUrl);
110 return nullptr;
112 } else {
113 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
114 MOZ_ASSERT(worker);
115 worker->AssertIsOnWorkerThread();
117 const auto& baseURL = worker->GetLocationInfo().mHref;
118 RefPtr<URL> url =
119 URL::Constructor(aGlobal.GetAsSupports(), aUrl, baseURL, aRv);
120 if (aRv.Failed()) {
121 return nullptr;
123 url->GetHref(parsedURL);
126 if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 &&
127 aStatus != 308) {
128 aRv.ThrowRangeError("Invalid redirect status code.");
129 return nullptr;
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;
136 ResponseInit init;
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())) {
141 return nullptr;
144 r->GetInternalHeaders()->Set("Location"_ns, parsedURL, aRv);
145 if (NS_WARN_IF(aRv.Failed())) {
146 return nullptr;
148 r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv);
149 MOZ_ASSERT(!aRv.Failed());
151 return r.forget();
154 /* static */ already_AddRefed<Response> Response::CreateAndInitializeAResponse(
155 const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody,
156 const nsACString& aDefaultContentType, const ResponseInit& aInit,
157 ErrorResult& aRv) {
158 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
160 if (NS_WARN_IF(!global)) {
161 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
162 return nullptr;
165 // Initialize a response, Step 1.
166 if (aInit.mStatus < 200 || aInit.mStatus > 599) {
167 aRv.ThrowRangeError("Invalid response status code.");
168 return nullptr;
171 // Initialize a response, Step 2: Check if the status text contains illegal
172 // characters
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>();
178 return nullptr;
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>();
184 return nullptr;
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
194 // interception.
195 if (NS_IsMainThread()) {
196 ChannelInfo info;
197 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
198 if (window) {
199 Document* doc = window->GetExtantDoc();
200 MOZ_ASSERT(doc);
201 info.InitFromDocument(doc);
203 principalInfo.reset(new mozilla::ipc::PrincipalInfo());
204 nsresult rv =
205 PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
206 if (NS_WARN_IF(NS_FAILED(rv))) {
207 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
208 return nullptr;
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.
226 } else {
227 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
228 MOZ_ASSERT(worker);
229 internalResponse->InitChannelInfo(worker->GetChannelInfo());
230 principalInfo =
231 MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo());
234 internalResponse->SetPrincipalInfo(std::move(principalInfo));
236 RefPtr<Response> r =
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);
246 if (aRv.Failed()) {
247 return nullptr;
250 internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
251 if (NS_WARN_IF(aRv.Failed())) {
252 return nullptr;
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.");
259 return nullptr;
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>();
276 return nullptr;
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;
286 } else {
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())) {
293 return nullptr;
296 } else {
297 uint64_t size = 0;
298 aRv = ExtractByteStreamFromBody(body, getter_AddRefs(bodyStream),
299 contentTypeWithCharset, size);
300 if (NS_WARN_IF(aRv.Failed())) {
301 return nullptr;
304 if (!aDefaultContentType.IsVoid()) {
305 contentTypeWithCharset = aDefaultContentType;
308 bodySize = size;
311 internalResponse->SetBody(bodyStream, bodySize);
313 if (!contentTypeWithCharset.IsVoid() &&
314 !internalResponse->Headers()->Has("Content-Type"_ns, aRv)) {
315 // Ignore Append() failing here.
316 ErrorResult error;
317 internalResponse->Headers()->Append("Content-Type"_ns,
318 contentTypeWithCharset, error);
319 error.SuppressException();
322 if (aRv.Failed()) {
323 return nullptr;
327 return r.forget();
330 /* static */
331 already_AddRefed<Response> Response::CreateFromJson(const GlobalObject& aGlobal,
332 JSContext* aCx,
333 JS::Handle<JS::Value> aData,
334 const ResponseInit& aInit,
335 ErrorResult& aRv) {
336 aRv.MightThrowJSException();
337 nsAutoString serializedValue;
338 if (!nsContentUtils::StringifyJSON(aCx, aData, serializedValue,
339 UndefinedIsVoidString)) {
340 aRv.StealExceptionFromJSContext(aCx);
341 return nullptr;
343 if (serializedValue.IsVoid()) {
344 aRv.ThrowTypeError<MSG_JSON_INVALID_VALUE>();
345 return nullptr;
347 Nullable<fetch::ResponseBodyInit> body;
348 body.SetValue().SetAsUSVString().ShareOrDependUpon(serializedValue);
349 return CreateAndInitializeAResponse(aGlobal, body, "application/json"_ns,
350 aInit, aRv);
353 /*static*/
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,
358 aRv);
361 already_AddRefed<Response> Response::Clone(JSContext* aCx, ErrorResult& aRv) {
362 bool bodyUsed = BodyUsed();
364 if (!bodyUsed && mReadableStreamBody) {
365 bool locked = mReadableStreamBody->Locked();
366 bodyUsed = locked;
369 if (bodyUsed) {
370 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
371 return nullptr;
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())) {
382 return nullptr;
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());
395 if (body) {
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
399 // lazily if needed.
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,
409 ErrorResult& aRv) {
410 if (BodyUsed()) {
411 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
412 return nullptr;
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())) {
423 return nullptr;
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());
436 if (body) {
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
440 // lazily if needed.
441 ref->SetReadableStreamBody(aCx, body);
442 ref->mFetchStreamReader = streamReader;
443 ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
446 return ref.forget();
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_() {
459 if (!mHeaders) {
460 mHeaders = new Headers(mOwner, mInternalResponse->Headers());
463 return mHeaders;
466 } // namespace mozilla::dom