Bug 1857841 - pt 3. Add a new page kind named "fresh" r=glandium
[gecko.git] / dom / fetch / Response.cpp
blob9e4cdf84ce15f73e812f63f97a428e29103a8e37
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 nsAString& aUrl,
88 uint16_t aStatus,
89 ErrorResult& aRv) {
90 nsAutoString parsedURL;
92 if (NS_IsMainThread()) {
93 nsIURI* baseURI = nullptr;
94 nsCOMPtr<nsPIDOMWindowInner> inner(
95 do_QueryInterface(aGlobal.GetAsSupports()));
96 Document* doc = inner ? inner->GetExtantDoc() : nullptr;
97 if (doc) {
98 baseURI = doc->GetBaseURI();
100 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
101 nsAutoCString url;
102 if (!AppendUTF16toUTF8(aUrl, url, fallible)) {
103 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
104 return nullptr;
107 nsCOMPtr<nsIURI> resolvedURI;
108 nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), url, nullptr, baseURI);
109 if (NS_WARN_IF(NS_FAILED(rv))) {
110 aRv.ThrowTypeError<MSG_INVALID_URL>(url);
111 return nullptr;
114 nsAutoCString spec;
115 rv = resolvedURI->GetSpec(spec);
116 if (NS_WARN_IF(NS_FAILED(rv))) {
117 aRv.ThrowTypeError<MSG_INVALID_URL>(url);
118 return nullptr;
121 CopyUTF8toUTF16(spec, parsedURL);
122 } else {
123 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
124 MOZ_ASSERT(worker);
125 worker->AssertIsOnWorkerThread();
127 NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
128 RefPtr<URL> url =
129 URL::Constructor(aGlobal.GetAsSupports(), aUrl, baseURL, aRv);
130 if (aRv.Failed()) {
131 return nullptr;
134 url->GetHref(parsedURL);
137 if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 &&
138 aStatus != 308) {
139 aRv.ThrowRangeError("Invalid redirect status code.");
140 return nullptr;
143 // We can't just pass nullptr for our null-valued Nullable, because the
144 // fetch::ResponseBodyInit is a non-temporary type due to the MOZ_RAII
145 // annotations on some of its members.
146 Nullable<fetch::ResponseBodyInit> body;
147 ResponseInit init;
148 init.mStatus = aStatus;
149 init.mStatusText.AssignASCII("");
150 RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv);
151 if (NS_WARN_IF(aRv.Failed())) {
152 return nullptr;
155 r->GetInternalHeaders()->Set("Location"_ns, NS_ConvertUTF16toUTF8(parsedURL),
156 aRv);
157 if (NS_WARN_IF(aRv.Failed())) {
158 return nullptr;
160 r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv);
161 MOZ_ASSERT(!aRv.Failed());
163 return r.forget();
166 /* static */ already_AddRefed<Response> Response::CreateAndInitializeAResponse(
167 const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody,
168 const nsACString& aDefaultContentType, const ResponseInit& aInit,
169 ErrorResult& aRv) {
170 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
172 if (NS_WARN_IF(!global)) {
173 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
174 return nullptr;
177 // Initialize a response, Step 1.
178 if (aInit.mStatus < 200 || aInit.mStatus > 599) {
179 aRv.ThrowRangeError("Invalid response status code.");
180 return nullptr;
183 // Initialize a response, Step 2: Check if the status text contains illegal
184 // characters
185 nsACString::const_iterator start, end;
186 aInit.mStatusText.BeginReading(start);
187 aInit.mStatusText.EndReading(end);
188 if (FindCharInReadable('\r', start, end)) {
189 aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>();
190 return nullptr;
192 // Reset iterator since FindCharInReadable advances it.
193 aInit.mStatusText.BeginReading(start);
194 if (FindCharInReadable('\n', start, end)) {
195 aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>();
196 return nullptr;
199 // Initialize a response, Step 3-4.
200 SafeRefPtr<InternalResponse> internalResponse =
201 MakeSafeRefPtr<InternalResponse>(aInit.mStatus, aInit.mStatusText);
203 UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo;
205 // Grab a valid channel info from the global so this response is 'valid' for
206 // interception.
207 if (NS_IsMainThread()) {
208 ChannelInfo info;
209 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
210 if (window) {
211 Document* doc = window->GetExtantDoc();
212 MOZ_ASSERT(doc);
213 info.InitFromDocument(doc);
215 principalInfo.reset(new mozilla::ipc::PrincipalInfo());
216 nsresult rv =
217 PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
218 if (NS_WARN_IF(NS_FAILED(rv))) {
219 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
220 return nullptr;
223 internalResponse->InitChannelInfo(info);
224 } else if (global->PrincipalOrNull()->IsSystemPrincipal()) {
225 info.InitFromChromeGlobal(global);
227 internalResponse->InitChannelInfo(info);
231 * The channel info is left uninitialized if neither the above `if` nor
232 * `else if` statements are executed; this could be because we're in a
233 * WebExtensions content script, where the global (i.e. `global`) is a
234 * wrapper, and the principal is an expanded principal. In this case,
235 * as far as I can tell, there's no way to get the security info, but we'd
236 * like the `Response` to be successfully constructed.
238 } else {
239 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
240 MOZ_ASSERT(worker);
241 internalResponse->InitChannelInfo(worker->GetChannelInfo());
242 principalInfo =
243 MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo());
246 internalResponse->SetPrincipalInfo(std::move(principalInfo));
248 RefPtr<Response> r =
249 new Response(global, internalResponse.clonePtr(), nullptr);
251 if (aInit.mHeaders.WasPassed()) {
252 internalResponse->Headers()->Clear();
254 // Instead of using Fill, create an object to allow the constructor to
255 // unwrap the HeadersInit.
256 RefPtr<Headers> headers =
257 Headers::Create(global, aInit.mHeaders.Value(), aRv);
258 if (aRv.Failed()) {
259 return nullptr;
262 internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
263 if (NS_WARN_IF(aRv.Failed())) {
264 return nullptr;
268 if (!aBody.IsNull()) {
269 if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) {
270 aRv.ThrowTypeError("Response body is given with a null body status.");
271 return nullptr;
274 nsCString contentTypeWithCharset;
275 contentTypeWithCharset.SetIsVoid(true);
276 nsCOMPtr<nsIInputStream> bodyStream;
277 int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE;
279 const fetch::ResponseBodyInit& body = aBody.Value();
280 if (body.IsReadableStream()) {
281 JSContext* cx = aGlobal.Context();
282 aRv.MightThrowJSException();
284 ReadableStream& readableStream = body.GetAsReadableStream();
286 if (readableStream.Locked() || readableStream.Disturbed()) {
287 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
288 return nullptr;
291 r->SetReadableStreamBody(cx, &readableStream);
293 // If this is a DOM generated ReadableStream, we can extract the
294 // inputStream directly.
295 if (nsIInputStream* underlyingSource =
296 readableStream.MaybeGetInputStreamIfUnread()) {
297 bodyStream = underlyingSource;
298 } else {
299 // If this is a JS-created ReadableStream, let's create a
300 // FetchStreamReader.
301 aRv = FetchStreamReader::Create(aGlobal.Context(), global,
302 getter_AddRefs(r->mFetchStreamReader),
303 getter_AddRefs(bodyStream));
304 if (NS_WARN_IF(aRv.Failed())) {
305 return nullptr;
308 } else {
309 uint64_t size = 0;
310 aRv = ExtractByteStreamFromBody(body, getter_AddRefs(bodyStream),
311 contentTypeWithCharset, size);
312 if (NS_WARN_IF(aRv.Failed())) {
313 return nullptr;
316 if (!aDefaultContentType.IsVoid()) {
317 contentTypeWithCharset = aDefaultContentType;
320 bodySize = size;
323 internalResponse->SetBody(bodyStream, bodySize);
325 if (!contentTypeWithCharset.IsVoid() &&
326 !internalResponse->Headers()->Has("Content-Type"_ns, aRv)) {
327 // Ignore Append() failing here.
328 ErrorResult error;
329 internalResponse->Headers()->Append("Content-Type"_ns,
330 contentTypeWithCharset, error);
331 error.SuppressException();
334 if (aRv.Failed()) {
335 return nullptr;
339 return r.forget();
342 /* static */
343 already_AddRefed<Response> Response::CreateFromJson(const GlobalObject& aGlobal,
344 JSContext* aCx,
345 JS::Handle<JS::Value> aData,
346 const ResponseInit& aInit,
347 ErrorResult& aRv) {
348 aRv.MightThrowJSException();
349 nsAutoString serializedValue;
350 if (!nsContentUtils::StringifyJSON(aCx, aData, serializedValue,
351 UndefinedIsVoidString)) {
352 aRv.StealExceptionFromJSContext(aCx);
353 return nullptr;
355 if (serializedValue.IsVoid()) {
356 aRv.ThrowTypeError<MSG_JSON_INVALID_VALUE>();
357 return nullptr;
359 Nullable<fetch::ResponseBodyInit> body;
360 body.SetValue().SetAsUSVString().ShareOrDependUpon(serializedValue);
361 return CreateAndInitializeAResponse(aGlobal, body, "application/json"_ns,
362 aInit, aRv);
365 /*static*/
366 already_AddRefed<Response> Response::Constructor(
367 const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody,
368 const ResponseInit& aInit, ErrorResult& aRv) {
369 return CreateAndInitializeAResponse(aGlobal, aBody, VoidCString(), aInit,
370 aRv);
373 already_AddRefed<Response> Response::Clone(JSContext* aCx, ErrorResult& aRv) {
374 bool bodyUsed = BodyUsed();
376 if (!bodyUsed && mReadableStreamBody) {
377 bool locked = mReadableStreamBody->Locked();
378 bodyUsed = locked;
381 if (bodyUsed) {
382 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
383 return nullptr;
386 RefPtr<FetchStreamReader> streamReader;
387 nsCOMPtr<nsIInputStream> inputStream;
389 RefPtr<ReadableStream> body;
390 MaybeTeeReadableStreamBody(aCx, getter_AddRefs(body),
391 getter_AddRefs(streamReader),
392 getter_AddRefs(inputStream), aRv);
393 if (NS_WARN_IF(aRv.Failed())) {
394 return nullptr;
397 MOZ_ASSERT_IF(body, streamReader);
398 MOZ_ASSERT_IF(body, inputStream);
400 SafeRefPtr<InternalResponse> ir =
401 mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream
402 : InternalResponse::eCloneInputStream);
404 RefPtr<Response> response =
405 new Response(mOwner, ir.clonePtr(), GetSignalImpl());
407 if (body) {
408 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
409 // if this body is a native stream. In this case the InternalResponse will
410 // have a clone of the native body and the ReadableStream will be created
411 // lazily if needed.
412 response->SetReadableStreamBody(aCx, body);
413 response->mFetchStreamReader = streamReader;
414 ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
417 return response.forget();
420 already_AddRefed<Response> Response::CloneUnfiltered(JSContext* aCx,
421 ErrorResult& aRv) {
422 if (BodyUsed()) {
423 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
424 return nullptr;
427 RefPtr<FetchStreamReader> streamReader;
428 nsCOMPtr<nsIInputStream> inputStream;
430 RefPtr<ReadableStream> body;
431 MaybeTeeReadableStreamBody(aCx, getter_AddRefs(body),
432 getter_AddRefs(streamReader),
433 getter_AddRefs(inputStream), aRv);
434 if (NS_WARN_IF(aRv.Failed())) {
435 return nullptr;
438 MOZ_ASSERT_IF(body, streamReader);
439 MOZ_ASSERT_IF(body, inputStream);
441 SafeRefPtr<InternalResponse> clone =
442 mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream
443 : InternalResponse::eCloneInputStream);
445 SafeRefPtr<InternalResponse> ir = clone->Unfiltered();
446 RefPtr<Response> ref = new Response(mOwner, ir.clonePtr(), GetSignalImpl());
448 if (body) {
449 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
450 // if this body is a native stream. In this case the InternalResponse will
451 // have a clone of the native body and the ReadableStream will be created
452 // lazily if needed.
453 ref->SetReadableStreamBody(aCx, body);
454 ref->mFetchStreamReader = streamReader;
455 ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
458 return ref.forget();
461 void Response::SetBody(nsIInputStream* aBody, int64_t aBodySize) {
462 MOZ_ASSERT(!BodyUsed());
463 mInternalResponse->SetBody(aBody, aBodySize);
466 SafeRefPtr<InternalResponse> Response::GetInternalResponse() const {
467 return mInternalResponse.clonePtr();
470 Headers* Response::Headers_() {
471 if (!mHeaders) {
472 mHeaders = new Headers(mOwner, mInternalResponse->Headers());
475 return mHeaders;
478 } // namespace mozilla::dom