Response.cpp (15902B)
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/. */ 6 7 #include "Response.h" 8 9 #include "BodyExtractor.h" 10 #include "FetchStreamReader.h" 11 #include "InternalResponse.h" 12 #include "mozilla/BasePrincipal.h" 13 #include "mozilla/ErrorResult.h" 14 #include "mozilla/HoldDropJSObjects.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/FetchBinding.h" 17 #include "mozilla/dom/Headers.h" 18 #include "mozilla/dom/Promise.h" 19 #include "mozilla/dom/ReadableStreamDefaultReader.h" 20 #include "mozilla/dom/ResponseBinding.h" 21 #include "mozilla/dom/URL.h" 22 #include "mozilla/dom/WorkerPrivate.h" 23 #include "nsDOMString.h" 24 #include "nsISupportsImpl.h" 25 #include "nsIURI.h" 26 #include "nsNetUtil.h" 27 #include "nsPIDOMWindow.h" 28 29 namespace mozilla::dom { 30 31 NS_IMPL_ADDREF_INHERITED(Response, FetchBody<Response>) 32 NS_IMPL_RELEASE_INHERITED(Response, FetchBody<Response>) 33 34 NS_IMPL_CYCLE_COLLECTION_CLASS(Response) 35 36 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Response, FetchBody<Response>) 37 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) 38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders) 39 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalImpl) 40 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader) 41 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 42 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 43 44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Response, FetchBody<Response>) 45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) 46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders) 47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalImpl) 48 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader) 49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 50 51 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Response, FetchBody<Response>) 52 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER 53 NS_IMPL_CYCLE_COLLECTION_TRACE_END 54 55 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response) 56 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 57 NS_INTERFACE_MAP_END_INHERITING(FetchBody<Response>) 58 59 Response::Response(nsIGlobalObject* aGlobal, 60 SafeRefPtr<InternalResponse> aInternalResponse, 61 AbortSignalImpl* aSignalImpl) 62 : FetchBody<Response>(aGlobal), 63 mInternalResponse(std::move(aInternalResponse)), 64 mSignalImpl(aSignalImpl) { 65 MOZ_ASSERT( 66 mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable || 67 mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response); 68 69 mozilla::HoldJSObjects(this); 70 } 71 72 Response::~Response() { mozilla::DropJSObjects(this); } 73 74 /* static */ 75 already_AddRefed<Response> Response::Error(const GlobalObject& aGlobal) { 76 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 77 RefPtr<Response> r = new Response( 78 global, InternalResponse::NetworkError(NS_ERROR_FAILURE), nullptr); 79 return r.forget(); 80 } 81 82 /* static */ 83 already_AddRefed<Response> Response::Redirect(const GlobalObject& aGlobal, 84 const nsACString& aUrl, 85 uint16_t aStatus, 86 ErrorResult& aRv) { 87 nsAutoCString parsedURL; 88 89 if (NS_IsMainThread()) { 90 nsIURI* baseURI = nullptr; 91 nsCOMPtr<nsPIDOMWindowInner> inner( 92 do_QueryInterface(aGlobal.GetAsSupports())); 93 if (Document* doc = inner ? inner->GetExtantDoc() : nullptr) { 94 baseURI = doc->GetBaseURI(); 95 } 96 nsCOMPtr<nsIURI> resolvedURI; 97 nsresult rv = 98 NS_NewURI(getter_AddRefs(resolvedURI), aUrl, nullptr, baseURI); 99 if (NS_WARN_IF(NS_FAILED(rv))) { 100 aRv.ThrowTypeError<MSG_INVALID_URL>(aUrl); 101 return nullptr; 102 } 103 104 rv = resolvedURI->GetSpec(parsedURL); 105 if (NS_WARN_IF(NS_FAILED(rv))) { 106 aRv.ThrowTypeError<MSG_INVALID_URL>(aUrl); 107 return nullptr; 108 } 109 } else { 110 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 111 MOZ_ASSERT(worker); 112 worker->AssertIsOnWorkerThread(); 113 114 const auto& baseURL = worker->GetLocationInfo().mHref; 115 RefPtr<URL> url = 116 URL::Constructor(aGlobal.GetAsSupports(), aUrl, baseURL, aRv); 117 if (aRv.Failed()) { 118 return nullptr; 119 } 120 url->GetHref(parsedURL); 121 } 122 123 if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 && 124 aStatus != 308) { 125 aRv.ThrowRangeError("Invalid redirect status code."); 126 return nullptr; 127 } 128 129 // We can't just pass nullptr for our null-valued Nullable, because the 130 // fetch::ResponseBodyInit is a non-temporary type due to the MOZ_RAII 131 // annotations on some of its members. 132 Nullable<fetch::ResponseBodyInit> body; 133 ResponseInit init; 134 init.mStatus = aStatus; 135 init.mStatusText.AssignASCII(""); 136 RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv); 137 if (NS_WARN_IF(aRv.Failed())) { 138 return nullptr; 139 } 140 141 r->GetInternalHeaders()->Set("Location"_ns, parsedURL, aRv); 142 if (NS_WARN_IF(aRv.Failed())) { 143 return nullptr; 144 } 145 r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv); 146 MOZ_ASSERT(!aRv.Failed()); 147 148 return r.forget(); 149 } 150 151 /* static */ already_AddRefed<Response> Response::CreateAndInitializeAResponse( 152 const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody, 153 const nsACString& aDefaultContentType, const ResponseInit& aInit, 154 ErrorResult& aRv) { 155 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 156 157 if (NS_WARN_IF(!global)) { 158 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 159 return nullptr; 160 } 161 162 // Initialize a response, Step 1. 163 if (aInit.mStatus < 200 || aInit.mStatus > 599) { 164 aRv.ThrowRangeError("Invalid response status code."); 165 return nullptr; 166 } 167 168 // Initialize a response, Step 2: Check if the status text contains illegal 169 // characters 170 nsACString::const_iterator start, end; 171 aInit.mStatusText.BeginReading(start); 172 aInit.mStatusText.EndReading(end); 173 if (FindCharInReadable('\r', start, end)) { 174 aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>(); 175 return nullptr; 176 } 177 // Reset iterator since FindCharInReadable advances it. 178 aInit.mStatusText.BeginReading(start); 179 if (FindCharInReadable('\n', start, end)) { 180 aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>(); 181 return nullptr; 182 } 183 184 // Initialize a response, Step 3-4. 185 SafeRefPtr<InternalResponse> internalResponse = 186 MakeSafeRefPtr<InternalResponse>(aInit.mStatus, aInit.mStatusText); 187 188 UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo; 189 190 // Grab a valid channel info from the global so this response is 'valid' for 191 // interception. 192 if (NS_IsMainThread()) { 193 ChannelInfo info; 194 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); 195 if (window) { 196 Document* doc = window->GetExtantDoc(); 197 MOZ_ASSERT(doc); 198 info.InitFromDocument(doc); 199 200 principalInfo.reset(new mozilla::ipc::PrincipalInfo()); 201 nsresult rv = 202 PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get()); 203 if (NS_WARN_IF(NS_FAILED(rv))) { 204 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); 205 return nullptr; 206 } 207 208 internalResponse->InitChannelInfo(info); 209 } else if (global->PrincipalOrNull()->IsSystemPrincipal()) { 210 info.InitFromChromeGlobal(global); 211 212 internalResponse->InitChannelInfo(info); 213 } 214 215 /** 216 * The channel info is left uninitialized if neither the above `if` nor 217 * `else if` statements are executed; this could be because we're in a 218 * WebExtensions content script, where the global (i.e. `global`) is a 219 * wrapper, and the principal is an expanded principal. In this case, 220 * as far as I can tell, there's no way to get the security info, but we'd 221 * like the `Response` to be successfully constructed. 222 */ 223 } else { 224 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 225 MOZ_ASSERT(worker); 226 internalResponse->InitChannelInfo(worker->GetChannelInfo()); 227 principalInfo = 228 MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo()); 229 } 230 231 internalResponse->SetPrincipalInfo(std::move(principalInfo)); 232 233 RefPtr<Response> r = 234 new Response(global, internalResponse.clonePtr(), nullptr); 235 236 if (aInit.mHeaders.WasPassed()) { 237 internalResponse->Headers()->Clear(); 238 239 // Instead of using Fill, create an object to allow the constructor to 240 // unwrap the HeadersInit. 241 RefPtr<Headers> headers = 242 Headers::Create(global, aInit.mHeaders.Value(), aRv); 243 if (aRv.Failed()) { 244 return nullptr; 245 } 246 247 internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv); 248 if (NS_WARN_IF(aRv.Failed())) { 249 return nullptr; 250 } 251 } 252 253 if (!aBody.IsNull()) { 254 if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) { 255 aRv.ThrowTypeError("Response body is given with a null body status."); 256 return nullptr; 257 } 258 259 nsCString contentTypeWithCharset; 260 contentTypeWithCharset.SetIsVoid(true); 261 nsCOMPtr<nsIInputStream> bodyStream; 262 int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE; 263 264 const fetch::ResponseBodyInit& body = aBody.Value(); 265 if (body.IsReadableStream()) { 266 JSContext* cx = aGlobal.Context(); 267 aRv.MightThrowJSException(); 268 269 ReadableStream& readableStream = body.GetAsReadableStream(); 270 271 if (readableStream.Locked() || readableStream.Disturbed()) { 272 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); 273 return nullptr; 274 } 275 276 r->SetReadableStreamBody(cx, &readableStream); 277 278 // If this is a DOM generated ReadableStream, we can extract the 279 // inputStream directly. 280 if (nsIInputStream* underlyingSource = 281 readableStream.MaybeGetInputStreamIfUnread()) { 282 bodyStream = underlyingSource; 283 } else { 284 // If this is a JS-created ReadableStream, let's create a 285 // FetchStreamReader. 286 aRv = FetchStreamReader::Create(aGlobal.Context(), global, 287 getter_AddRefs(r->mFetchStreamReader), 288 getter_AddRefs(bodyStream)); 289 if (NS_WARN_IF(aRv.Failed())) { 290 return nullptr; 291 } 292 } 293 } else { 294 uint64_t size = 0; 295 aRv = ExtractByteStreamFromBody(body, getter_AddRefs(bodyStream), 296 contentTypeWithCharset, size); 297 if (NS_WARN_IF(aRv.Failed())) { 298 return nullptr; 299 } 300 301 if (!aDefaultContentType.IsVoid()) { 302 contentTypeWithCharset = aDefaultContentType; 303 } 304 305 bodySize = size; 306 } 307 308 internalResponse->SetBody(bodyStream, bodySize); 309 310 if (!contentTypeWithCharset.IsVoid() && 311 !internalResponse->Headers()->Has("Content-Type"_ns, aRv)) { 312 // Ignore Append() failing here. 313 ErrorResult error; 314 internalResponse->Headers()->Append("Content-Type"_ns, 315 contentTypeWithCharset, error); 316 error.SuppressException(); 317 } 318 319 if (aRv.Failed()) { 320 return nullptr; 321 } 322 } 323 324 return r.forget(); 325 } 326 327 /* static */ 328 already_AddRefed<Response> Response::CreateFromJson(const GlobalObject& aGlobal, 329 JSContext* aCx, 330 JS::Handle<JS::Value> aData, 331 const ResponseInit& aInit, 332 ErrorResult& aRv) { 333 aRv.MightThrowJSException(); 334 nsAutoString serializedValue; 335 if (!nsContentUtils::StringifyJSON(aCx, aData, serializedValue, 336 UndefinedIsVoidString)) { 337 aRv.StealExceptionFromJSContext(aCx); 338 return nullptr; 339 } 340 if (serializedValue.IsVoid()) { 341 aRv.ThrowTypeError<MSG_JSON_INVALID_VALUE>(); 342 return nullptr; 343 } 344 Nullable<fetch::ResponseBodyInit> body; 345 body.SetValue().SetAsUSVString().ShareOrDependUpon(serializedValue); 346 return CreateAndInitializeAResponse(aGlobal, body, "application/json"_ns, 347 aInit, aRv); 348 } 349 350 /*static*/ 351 already_AddRefed<Response> Response::Constructor( 352 const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody, 353 const ResponseInit& aInit, ErrorResult& aRv) { 354 return CreateAndInitializeAResponse(aGlobal, aBody, VoidCString(), aInit, 355 aRv); 356 } 357 358 already_AddRefed<Response> Response::Clone(JSContext* aCx, ErrorResult& aRv) { 359 bool bodyUsed = BodyUsed(); 360 361 if (!bodyUsed && mReadableStreamBody) { 362 bool locked = mReadableStreamBody->Locked(); 363 bodyUsed = locked; 364 } 365 366 if (bodyUsed) { 367 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); 368 return nullptr; 369 } 370 371 RefPtr<FetchStreamReader> streamReader; 372 nsCOMPtr<nsIInputStream> inputStream; 373 374 RefPtr<ReadableStream> body; 375 MaybeTeeReadableStreamBody(aCx, getter_AddRefs(body), 376 getter_AddRefs(streamReader), 377 getter_AddRefs(inputStream), aRv); 378 if (NS_WARN_IF(aRv.Failed())) { 379 return nullptr; 380 } 381 382 MOZ_ASSERT_IF(body, streamReader); 383 MOZ_ASSERT_IF(body, inputStream); 384 385 SafeRefPtr<InternalResponse> ir = 386 mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream 387 : InternalResponse::eCloneInputStream); 388 389 RefPtr<Response> response = 390 new Response(mOwner, ir.clonePtr(), GetSignalImpl()); 391 392 if (body) { 393 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody 394 // if this body is a native stream. In this case the InternalResponse will 395 // have a clone of the native body and the ReadableStream will be created 396 // lazily if needed. 397 response->SetReadableStreamBody(aCx, body); 398 response->mFetchStreamReader = streamReader; 399 ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE); 400 } 401 402 return response.forget(); 403 } 404 405 already_AddRefed<Response> Response::CloneUnfiltered(JSContext* aCx, 406 ErrorResult& aRv) { 407 if (BodyUsed()) { 408 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); 409 return nullptr; 410 } 411 412 RefPtr<FetchStreamReader> streamReader; 413 nsCOMPtr<nsIInputStream> inputStream; 414 415 RefPtr<ReadableStream> body; 416 MaybeTeeReadableStreamBody(aCx, getter_AddRefs(body), 417 getter_AddRefs(streamReader), 418 getter_AddRefs(inputStream), aRv); 419 if (NS_WARN_IF(aRv.Failed())) { 420 return nullptr; 421 } 422 423 MOZ_ASSERT_IF(body, streamReader); 424 MOZ_ASSERT_IF(body, inputStream); 425 426 SafeRefPtr<InternalResponse> clone = 427 mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream 428 : InternalResponse::eCloneInputStream); 429 430 SafeRefPtr<InternalResponse> ir = clone->Unfiltered(); 431 RefPtr<Response> ref = new Response(mOwner, ir.clonePtr(), GetSignalImpl()); 432 433 if (body) { 434 // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody 435 // if this body is a native stream. In this case the InternalResponse will 436 // have a clone of the native body and the ReadableStream will be created 437 // lazily if needed. 438 ref->SetReadableStreamBody(aCx, body); 439 ref->mFetchStreamReader = streamReader; 440 ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE); 441 } 442 443 return ref.forget(); 444 } 445 446 void Response::SetBody(nsIInputStream* aBody, int64_t aBodySize) { 447 MOZ_ASSERT(!BodyUsed()); 448 mInternalResponse->SetBody(aBody, aBodySize); 449 } 450 451 SafeRefPtr<InternalResponse> Response::GetInternalResponse() const { 452 return mInternalResponse.clonePtr(); 453 } 454 455 Headers* Response::Headers_() { 456 if (!mHeaders) { 457 mHeaders = new Headers(mOwner, mInternalResponse->Headers()); 458 } 459 460 return mHeaders; 461 } 462 463 } // namespace mozilla::dom