Bug 1773649 - Update metadata for setpointercapture test on mac. a=testonly CLOSED...
[gecko.git] / dom / fetch / Response.cpp
bloba63164c3976abd433475c4745e8af0f1c38fb186
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 #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) {
73 MOZ_ASSERT(
74 mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
75 mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
77 mozilla::HoldJSObjects(this);
80 Response::~Response() { mozilla::DropJSObjects(this); }
82 /* static */
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);
87 return r.forget();
90 /* static */
91 already_AddRefed<Response> Response::Redirect(const GlobalObject& aGlobal,
92 const nsAString& aUrl,
93 uint16_t aStatus,
94 ErrorResult& aRv) {
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;
102 if (doc) {
103 baseURI = doc->GetBaseURI();
105 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
106 nsAutoCString url;
107 if (!AppendUTF16toUTF8(aUrl, url, fallible)) {
108 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
109 return nullptr;
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);
116 return nullptr;
119 nsAutoCString spec;
120 rv = resolvedURI->GetSpec(spec);
121 if (NS_WARN_IF(NS_FAILED(rv))) {
122 aRv.ThrowTypeError<MSG_INVALID_URL>(url);
123 return nullptr;
126 CopyUTF8toUTF16(spec, parsedURL);
127 } else {
128 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
129 MOZ_ASSERT(worker);
130 worker->AssertIsOnWorkerThread();
132 NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
133 RefPtr<URL> url =
134 URL::Constructor(aGlobal.GetAsSupports(), aUrl, baseURL, aRv);
135 if (aRv.Failed()) {
136 return nullptr;
139 url->GetHref(parsedURL);
142 if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 &&
143 aStatus != 308) {
144 aRv.ThrowRangeError("Invalid redirect status code.");
145 return nullptr;
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;
152 ResponseInit init;
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())) {
157 return nullptr;
160 r->GetInternalHeaders()->Set("Location"_ns, NS_ConvertUTF16toUTF8(parsedURL),
161 aRv);
162 if (NS_WARN_IF(aRv.Failed())) {
163 return nullptr;
165 r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv);
166 MOZ_ASSERT(!aRv.Failed());
168 return r.forget();
171 /*static*/
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);
179 return nullptr;
182 if (aInit.mStatus < 200 || aInit.mStatus > 599) {
183 aRv.ThrowRangeError("Invalid response status code.");
184 return nullptr;
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>();
193 return nullptr;
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>();
199 return nullptr;
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
208 // interception.
209 if (NS_IsMainThread()) {
210 ChannelInfo info;
211 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
212 if (window) {
213 Document* doc = window->GetExtantDoc();
214 MOZ_ASSERT(doc);
215 info.InitFromDocument(doc);
217 principalInfo.reset(new mozilla::ipc::PrincipalInfo());
218 nsresult rv =
219 PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
220 if (NS_WARN_IF(NS_FAILED(rv))) {
221 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
222 return nullptr;
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.
240 } else {
241 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
242 MOZ_ASSERT(worker);
243 internalResponse->InitChannelInfo(worker->GetChannelInfo());
244 principalInfo =
245 MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo());
248 internalResponse->SetPrincipalInfo(std::move(principalInfo));
250 RefPtr<Response> r =
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);
260 if (aRv.Failed()) {
261 return nullptr;
264 internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
265 if (NS_WARN_IF(aRv.Failed())) {
266 return nullptr;
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.");
273 return nullptr;
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>();
289 return nullptr;
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())) {
305 return nullptr;
307 } else {
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())) {
314 return nullptr;
317 } else {
318 uint64_t size = 0;
319 aRv = ExtractByteStreamFromBody(body, getter_AddRefs(bodyStream),
320 contentTypeWithCharset, size);
321 if (NS_WARN_IF(aRv.Failed())) {
322 return nullptr;
325 bodySize = size;
328 internalResponse->SetBody(bodyStream, bodySize);
330 if (!contentTypeWithCharset.IsVoid() &&
331 !internalResponse->Headers()->Has("Content-Type"_ns, aRv)) {
332 // Ignore Append() failing here.
333 ErrorResult error;
334 internalResponse->Headers()->Append("Content-Type"_ns,
335 contentTypeWithCharset, error);
336 error.SuppressException();
339 if (aRv.Failed()) {
340 return nullptr;
344 return r.forget();
347 already_AddRefed<Response> Response::Clone(JSContext* aCx, ErrorResult& aRv) {
348 bool bodyUsed = GetBodyUsed(aRv);
349 if (NS_WARN_IF(aRv.Failed())) {
350 return nullptr;
353 if (!bodyUsed && mReadableStreamBody) {
354 bool locked = mReadableStreamBody->Locked();
355 bodyUsed = locked;
358 if (bodyUsed) {
359 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
360 return nullptr;
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())) {
371 return nullptr;
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());
384 if (body) {
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
388 // lazily if needed.
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,
398 ErrorResult& aRv) {
399 if (GetBodyUsed(aRv)) {
400 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
401 return nullptr;
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())) {
412 return nullptr;
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());
425 if (body) {
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
429 // lazily if needed.
430 ref->SetReadableStreamBody(aCx, body);
431 ref->mFetchStreamReader = streamReader;
432 ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
435 return ref.forget();
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_() {
448 if (!mHeaders) {
449 mHeaders = new Headers(mOwner, mInternalResponse->Headers());
452 return mHeaders;
455 } // namespace mozilla::dom