Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / fetch / Response.cpp
blobbb2a937a27d2e0481239e99c6a5748ec4e3c4c5f
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/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) {
75 MOZ_ASSERT(
76 aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
77 aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
79 mozilla::HoldJSObjects(this);
82 Response::~Response() { mozilla::DropJSObjects(this); }
84 /* static */
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);
90 return r.forget();
93 /* static */
94 already_AddRefed<Response> Response::Redirect(const GlobalObject& aGlobal,
95 const nsAString& aUrl,
96 uint16_t aStatus,
97 ErrorResult& aRv) {
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;
105 if (doc) {
106 baseURI = doc->GetBaseURI();
108 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
109 nsAutoCString url;
110 if (!AppendUTF16toUTF8(aUrl, url, fallible)) {
111 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
112 return nullptr;
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);
119 return nullptr;
122 nsAutoCString spec;
123 rv = resolvedURI->GetSpec(spec);
124 if (NS_WARN_IF(NS_FAILED(rv))) {
125 aRv.ThrowTypeError<MSG_INVALID_URL>(url);
126 return nullptr;
129 CopyUTF8toUTF16(spec, parsedURL);
130 } else {
131 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
132 MOZ_ASSERT(worker);
133 worker->AssertIsOnWorkerThread();
135 NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
136 RefPtr<URL> url =
137 URL::Constructor(aGlobal.GetAsSupports(), aUrl, baseURL, aRv);
138 if (aRv.Failed()) {
139 return nullptr;
142 url->GetHref(parsedURL);
145 if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 &&
146 aStatus != 308) {
147 aRv.ThrowRangeError("Invalid redirect status code.");
148 return nullptr;
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;
155 ResponseInit init;
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())) {
160 return nullptr;
163 r->GetInternalHeaders()->Set("Location"_ns, NS_ConvertUTF16toUTF8(parsedURL),
164 aRv);
165 if (NS_WARN_IF(aRv.Failed())) {
166 return nullptr;
168 r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv);
169 MOZ_ASSERT(!aRv.Failed());
171 return r.forget();
174 /*static*/
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);
182 return nullptr;
185 if (aInit.mStatus < 200 || aInit.mStatus > 599) {
186 aRv.ThrowRangeError("Invalid response status code.");
187 return nullptr;
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>();
196 return nullptr;
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>();
202 return nullptr;
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
211 // interception.
212 if (NS_IsMainThread()) {
213 ChannelInfo info;
214 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
215 if (window) {
216 Document* doc = window->GetExtantDoc();
217 MOZ_ASSERT(doc);
218 info.InitFromDocument(doc);
220 principalInfo.reset(new mozilla::ipc::PrincipalInfo());
221 nsresult rv =
222 PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
223 if (NS_WARN_IF(NS_FAILED(rv))) {
224 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
225 return nullptr;
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.
243 } else {
244 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
245 MOZ_ASSERT(worker);
246 internalResponse->InitChannelInfo(worker->GetChannelInfo());
247 principalInfo =
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);
262 if (aRv.Failed()) {
263 return nullptr;
266 internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
267 if (NS_WARN_IF(aRv.Failed())) {
268 return nullptr;
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.");
275 return nullptr;
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());
291 bool disturbed;
292 bool locked;
293 if (!JS::ReadableStreamIsDisturbed(cx, readableStreamObj, &disturbed) ||
294 !JS::ReadableStreamIsLocked(cx, readableStreamObj, &locked)) {
295 aRv.StealExceptionFromJSContext(cx);
296 return nullptr;
298 if (disturbed || locked) {
299 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
300 return nullptr;
303 r->SetReadableStreamBody(cx, readableStreamObj);
305 JS::ReadableStreamMode streamMode;
306 if (!JS::ReadableStreamGetMode(cx, readableStreamObj, &streamMode)) {
307 aRv.StealExceptionFromJSContext(cx);
308 return nullptr;
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);
317 return nullptr;
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);
330 return nullptr;
332 if (NS_WARN_IF(aRv.Failed())) {
333 return nullptr;
335 } else {
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())) {
342 return nullptr;
345 } else {
346 uint64_t size = 0;
347 aRv = ExtractByteStreamFromBody(body, getter_AddRefs(bodyStream),
348 contentTypeWithCharset, size);
349 if (NS_WARN_IF(aRv.Failed())) {
350 return nullptr;
353 bodySize = size;
356 internalResponse->SetBody(bodyStream, bodySize);
358 if (!contentTypeWithCharset.IsVoid() &&
359 !internalResponse->Headers()->Has("Content-Type"_ns, aRv)) {
360 // Ignore Append() failing here.
361 ErrorResult error;
362 internalResponse->Headers()->Append("Content-Type"_ns,
363 contentTypeWithCharset, error);
364 error.SuppressException();
367 if (aRv.Failed()) {
368 return nullptr;
372 return r.forget();
375 already_AddRefed<Response> Response::Clone(JSContext* aCx, ErrorResult& aRv) {
376 bool bodyUsed = GetBodyUsed(aRv);
377 if (NS_WARN_IF(aRv.Failed())) {
378 return nullptr;
381 if (!bodyUsed && mReadableStreamBody) {
382 aRv.MightThrowJSException();
384 AutoJSAPI jsapi;
385 if (!jsapi.Init(mOwner)) {
386 aRv.Throw(NS_ERROR_FAILURE);
387 return nullptr;
390 JSContext* cx = jsapi.cx();
391 JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
392 bool locked;
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);
397 return nullptr;
400 bodyUsed = locked;
403 if (bodyUsed) {
404 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
405 return nullptr;
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())) {
415 return nullptr;
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());
427 if (body) {
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
431 // lazily if needed.
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,
441 ErrorResult& aRv) {
442 if (GetBodyUsed(aRv)) {
443 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
444 return nullptr;
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())) {
454 return nullptr;
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());
467 if (body) {
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
471 // lazily if needed.
472 ref->SetReadableStreamBody(aCx, body);
473 ref->mFetchStreamReader = streamReader;
474 ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
477 return ref.forget();
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;
487 return ref.forget();
490 Headers* Response::Headers_() {
491 if (!mHeaders) {
492 mHeaders = new Headers(mOwner, mInternalResponse->Headers());
495 return mHeaders;
498 } // namespace mozilla::dom