Request.cpp (16647B)
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 "Request.h" 8 9 #include "js/Value.h" 10 #include "mozilla/ErrorResult.h" 11 #include "mozilla/StaticPrefs_network.h" 12 #include "mozilla/dom/Fetch.h" 13 #include "mozilla/dom/FetchUtil.h" 14 #include "mozilla/dom/Headers.h" 15 #include "mozilla/dom/Promise.h" 16 #include "mozilla/dom/ReadableStreamDefaultReader.h" 17 #include "mozilla/dom/URL.h" 18 #include "mozilla/dom/WindowContext.h" 19 #include "mozilla/dom/WorkerPrivate.h" 20 #include "mozilla/dom/WorkerRunnable.h" 21 #include "mozilla/ipc/PBackgroundSharedTypes.h" 22 #include "nsIURI.h" 23 #include "nsNetUtil.h" 24 #include "nsPIDOMWindow.h" 25 26 namespace mozilla::dom { 27 28 NS_IMPL_ADDREF_INHERITED(Request, FetchBody<Request>) 29 NS_IMPL_RELEASE_INHERITED(Request, FetchBody<Request>) 30 31 // Can't use _INHERITED macro here because FetchBody<Request> is abstract. 32 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Request) 33 34 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Request, FetchBody<Request>) 35 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) 36 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders) 37 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignal) 38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader) 39 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 40 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 41 42 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Request, FetchBody<Request>) 43 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) 44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders) 45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignal) 46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader) 47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 48 49 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request) 50 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 51 NS_INTERFACE_MAP_END_INHERITING(FetchBody<Request>) 52 53 Request::Request(nsIGlobalObject* aOwner, SafeRefPtr<InternalRequest> aRequest, 54 AbortSignal* aSignal) 55 : FetchBody<Request>(aOwner), mRequest(std::move(aRequest)) { 56 MOZ_ASSERT(mRequest->Headers()->Guard() == HeadersGuardEnum::Immutable || 57 mRequest->Headers()->Guard() == HeadersGuardEnum::Request || 58 mRequest->Headers()->Guard() == HeadersGuardEnum::Request_no_cors); 59 if (aSignal) { 60 // If we don't have a signal as argument, we will create it when required by 61 // content, otherwise the Request's signal must follow what has been passed. 62 AutoTArray<OwningNonNull<AbortSignal>, 1> array{OwningNonNull(*aSignal)}; 63 mSignal = AbortSignal::Any(aOwner, array, [](nsIGlobalObject* aGlobal) { 64 return AbortSignal::Create(aGlobal, SignalAborted::No, 65 JS::UndefinedHandleValue); 66 }); 67 } 68 } 69 70 Request::~Request() = default; 71 72 SafeRefPtr<InternalRequest> Request::GetInternalRequest() { 73 return mRequest.clonePtr(); 74 } 75 76 namespace { 77 already_AddRefed<nsIURI> ParseURL(nsIGlobalObject* aGlobal, 78 const nsACString& aInput, ErrorResult& aRv) { 79 nsCOMPtr<nsIURI> baseURI; 80 if (NS_IsMainThread()) { 81 nsCOMPtr<nsPIDOMWindowInner> inner(do_QueryInterface(aGlobal)); 82 Document* doc = inner ? inner->GetExtantDoc() : nullptr; 83 baseURI = doc ? doc->GetBaseURI() : nullptr; 84 } else { 85 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 86 baseURI = worker->GetBaseURI(); 87 } 88 89 nsCOMPtr<nsIURI> uri; 90 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aInput, nullptr, baseURI))) { 91 aRv.ThrowTypeError<MSG_INVALID_URL>(aInput); 92 return nullptr; 93 } 94 return uri.forget(); 95 } 96 97 void GetRequestURL(nsIGlobalObject* aGlobal, const nsACString& aInput, 98 nsACString& aRequestURL, nsACString& aURLfragment, 99 ErrorResult& aRv) { 100 nsCOMPtr<nsIURI> resolvedURI = ParseURL(aGlobal, aInput, aRv); 101 if (aRv.Failed()) { 102 return; 103 } 104 // This fails with URIs with weird protocols, even when they are valid, 105 // so we ignore the failure 106 nsAutoCString credentials; 107 (void)resolvedURI->GetUserPass(credentials); 108 if (!credentials.IsEmpty()) { 109 aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput); 110 return; 111 } 112 113 nsCOMPtr<nsIURI> resolvedURIClone; 114 aRv = NS_GetURIWithoutRef(resolvedURI, getter_AddRefs(resolvedURIClone)); 115 if (NS_WARN_IF(aRv.Failed())) { 116 return; 117 } 118 aRv = resolvedURIClone->GetSpec(aRequestURL); 119 if (NS_WARN_IF(aRv.Failed())) { 120 return; 121 } 122 123 // Get the fragment from nsIURI. 124 aRv = resolvedURI->GetRef(aURLfragment); 125 if (NS_WARN_IF(aRv.Failed())) { 126 return; 127 } 128 } 129 } // namespace 130 131 /*static*/ 132 SafeRefPtr<Request> Request::Constructor(const GlobalObject& aGlobal, 133 const RequestOrUTF8String& aInput, 134 const RequestInit& aInit, 135 ErrorResult& aRv) { 136 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 137 return Constructor(global, aGlobal.Context(), aInput, aInit, 138 aGlobal.CallerType(), aRv); 139 } 140 141 /*static*/ 142 SafeRefPtr<Request> Request::Constructor( 143 nsIGlobalObject* aGlobal, JSContext* aCx, const RequestOrUTF8String& aInput, 144 const RequestInit& aInit, CallerType aCallerType, ErrorResult& aRv) { 145 bool hasCopiedBody = false; 146 SafeRefPtr<InternalRequest> request; 147 148 RefPtr<AbortSignal> signal; 149 bool bodyFromInit = false; 150 151 if (aInput.IsRequest()) { 152 RefPtr<Request> inputReq = &aInput.GetAsRequest(); 153 nsCOMPtr<nsIInputStream> body; 154 155 if (aInit.mBody.WasPassed() && !aInit.mBody.Value().IsNull()) { 156 bodyFromInit = true; 157 hasCopiedBody = true; 158 } else { 159 inputReq->GetBody(getter_AddRefs(body)); 160 if (inputReq->BodyUsed()) { 161 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); 162 return nullptr; 163 } 164 165 // The body will be copied when GetRequestConstructorCopy() is executed. 166 if (body) { 167 hasCopiedBody = true; 168 } 169 } 170 171 request = inputReq->GetInternalRequest(); 172 signal = inputReq->GetOrCreateSignal(); 173 } else { 174 // aInput is UTF8String. 175 // We need to get url before we create a InternalRequest. 176 const nsACString& input = aInput.GetAsUTF8String(); 177 nsAutoCString requestURL; 178 nsCString fragment; 179 GetRequestURL(aGlobal, input, requestURL, fragment, aRv); 180 if (aRv.Failed()) { 181 return nullptr; 182 } 183 request = MakeSafeRefPtr<InternalRequest>(requestURL, fragment); 184 } 185 request = request->GetRequestConstructorCopy(aGlobal, aRv); 186 if (NS_WARN_IF(aRv.Failed())) { 187 return nullptr; 188 } 189 Maybe<RequestMode> mode; 190 if (aInit.mMode.WasPassed()) { 191 if (aInit.mMode.Value() == RequestMode::Navigate) { 192 aRv.ThrowTypeError<MSG_INVALID_REQUEST_MODE>("navigate"); 193 return nullptr; 194 } 195 196 mode.emplace(aInit.mMode.Value()); 197 } 198 Maybe<RequestCredentials> credentials; 199 if (aInit.mCredentials.WasPassed()) { 200 credentials.emplace(aInit.mCredentials.Value()); 201 } 202 Maybe<RequestCache> cache; 203 if (aInit.mCache.WasPassed()) { 204 cache.emplace(aInit.mCache.Value()); 205 } 206 if (aInput.IsUTF8String()) { 207 if (mode.isNothing()) { 208 mode.emplace(RequestMode::Cors); 209 } 210 if (credentials.isNothing()) { 211 if (aCallerType == CallerType::System && 212 StaticPrefs::network_fetch_systemDefaultsToOmittingCredentials()) { 213 credentials.emplace(RequestCredentials::Omit); 214 } else { 215 credentials.emplace(RequestCredentials::Same_origin); 216 } 217 } 218 if (cache.isNothing()) { 219 cache.emplace(RequestCache::Default); 220 } 221 } 222 223 if (aInit.IsAnyMemberPresent() && request->Mode() == RequestMode::Navigate) { 224 mode = Some(RequestMode::Same_origin); 225 } 226 227 if (aInit.IsAnyMemberPresent()) { 228 request->SetReferrer(nsLiteralCString(kFETCH_CLIENT_REFERRER_STR)); 229 request->SetReferrerPolicy(ReferrerPolicy::_empty); 230 } 231 if (aInit.mReferrer.WasPassed()) { 232 const nsCString& referrer = aInit.mReferrer.Value(); 233 if (referrer.IsEmpty()) { 234 request->SetReferrer(""_ns); 235 } else { 236 nsCOMPtr<nsIURI> referrerURI = ParseURL(aGlobal, referrer, aRv); 237 if (NS_WARN_IF(aRv.Failed())) { 238 aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer); 239 return nullptr; 240 } 241 242 nsAutoCString spec; 243 referrerURI->GetSpec(spec); 244 if (!spec.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) { 245 nsCOMPtr<nsIPrincipal> principal = aGlobal->PrincipalOrNull(); 246 if (principal) { 247 nsresult rv = 248 principal->CheckMayLoad(referrerURI, 249 /* allowIfInheritsPrincipal */ false); 250 if (NS_FAILED(rv)) { 251 spec.AssignLiteral(kFETCH_CLIENT_REFERRER_STR); 252 } 253 } 254 } 255 256 request->SetReferrer(spec); 257 } 258 } 259 260 if (aInit.mReferrerPolicy.WasPassed()) { 261 request->SetReferrerPolicy(aInit.mReferrerPolicy.Value()); 262 } 263 264 if (aInit.mSignal.WasPassed()) { 265 signal = aInit.mSignal.Value(); 266 } 267 268 // https://fetch.spec.whatwg.org/#dom-global-fetch 269 // https://fetch.spec.whatwg.org/#dom-request 270 // The priority of init overrides input's priority. 271 if (aInit.mPriority.WasPassed()) { 272 request->SetPriorityMode(aInit.mPriority.Value()); 273 } 274 275 UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo; 276 nsILoadInfo::CrossOriginEmbedderPolicy coep = 277 nsILoadInfo::EMBEDDER_POLICY_NULL; 278 279 if (NS_IsMainThread()) { 280 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal); 281 if (window) { 282 nsCOMPtr<Document> doc; 283 doc = window->GetExtantDoc(); 284 if (doc) { 285 request->SetEnvironmentReferrerPolicy(doc->GetReferrerPolicy()); 286 287 principalInfo.reset(new mozilla::ipc::PrincipalInfo()); 288 nsresult rv = 289 PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get()); 290 if (NS_WARN_IF(NS_FAILED(rv))) { 291 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); 292 return nullptr; 293 } 294 } 295 if (window->GetWindowContext()) { 296 coep = window->GetWindowContext()->GetEmbedderPolicy(); 297 } 298 } 299 } else { 300 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 301 if (worker) { 302 worker->AssertIsOnWorkerThread(); 303 request->SetEnvironmentReferrerPolicy(worker->GetReferrerPolicy()); 304 principalInfo = 305 MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo()); 306 coep = worker->GetEmbedderPolicy(); 307 // For dedicated worker, the response must respect the owner's COEP. 308 if (coep == nsILoadInfo::EMBEDDER_POLICY_NULL && 309 worker->IsDedicatedWorker()) { 310 coep = worker->GetOwnerEmbedderPolicy(); 311 } 312 } 313 } 314 315 request->SetPrincipalInfo(std::move(principalInfo)); 316 request->SetEmbedderPolicy(coep); 317 318 if (mode.isSome()) { 319 request->SetMode(mode.value()); 320 } 321 322 if (credentials.isSome()) { 323 request->SetCredentialsMode(credentials.value()); 324 } 325 326 if (cache.isSome()) { 327 if (cache.value() == RequestCache::Only_if_cached && 328 request->Mode() != RequestMode::Same_origin) { 329 aRv.ThrowTypeError<MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN>( 330 GetEnumString(request->Mode())); 331 return nullptr; 332 } 333 request->SetCacheMode(cache.value()); 334 } 335 336 if (aInit.mRedirect.WasPassed()) { 337 request->SetRedirectMode(aInit.mRedirect.Value()); 338 } 339 340 if (aInit.mIntegrity.WasPassed()) { 341 request->SetIntegrity(aInit.mIntegrity.Value()); 342 } 343 344 if (aInit.mKeepalive.WasPassed()) { 345 request->SetKeepalive(aInit.mKeepalive.Value()); 346 } 347 348 if (aInit.mMozErrors.WasPassed() && aInit.mMozErrors.Value()) { 349 request->SetMozErrors(); 350 } 351 352 if (aInit.mTriggeringPrincipal.WasPassed() && 353 aInit.mTriggeringPrincipal.Value()) { 354 request->SetTriggeringPrincipal(aInit.mTriggeringPrincipal.Value()); 355 } 356 357 if (aInit.mNeverTaint.WasPassed()) { 358 if (!XRE_IsParentProcess()) { 359 aRv.ThrowNotAllowedError( 360 "Taint has to happen outside of the parent process."); 361 return nullptr; 362 } 363 request->SetNeverTaint(aInit.mNeverTaint.Value()); 364 } 365 366 // Request constructor step 14. 367 if (aInit.mMethod.WasPassed()) { 368 nsAutoCString method(aInit.mMethod.Value()); 369 370 // Step 14.1. Disallow forbidden methods, and anything that is not a HTTP 371 // token, since HTTP states that Method may be any of the defined values or 372 // a token (extension method). 373 nsAutoCString outMethod; 374 nsresult rv = FetchUtil::GetValidRequestMethod(method, outMethod); 375 if (NS_FAILED(rv)) { 376 aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(method); 377 return nullptr; 378 } 379 380 // Step 14.2 381 request->SetMethod(outMethod); 382 } 383 384 RefPtr<InternalHeaders> requestHeaders = request->Headers(); 385 386 RefPtr<InternalHeaders> headers; 387 if (aInit.mHeaders.WasPassed()) { 388 RefPtr<Headers> h = Headers::Create(aGlobal, aInit.mHeaders.Value(), aRv); 389 if (aRv.Failed()) { 390 return nullptr; 391 } 392 headers = h->GetInternalHeaders(); 393 } else { 394 headers = new InternalHeaders(*requestHeaders); 395 } 396 397 requestHeaders->Clear(); 398 // From "Let r be a new Request object associated with request and a new 399 // Headers object whose guard is "request"." 400 requestHeaders->SetGuard(HeadersGuardEnum::Request, aRv); 401 MOZ_ASSERT(!aRv.Failed()); 402 403 if (request->Mode() == RequestMode::No_cors) { 404 if (!request->HasSimpleMethod()) { 405 nsAutoCString method; 406 request->GetMethod(method); 407 aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(method); 408 return nullptr; 409 } 410 411 requestHeaders->SetGuard(HeadersGuardEnum::Request_no_cors, aRv); 412 if (aRv.Failed()) { 413 return nullptr; 414 } 415 } 416 417 requestHeaders->Fill(*headers, aRv); 418 if (aRv.Failed()) { 419 return nullptr; 420 } 421 422 if ((aInit.mBody.WasPassed() && !aInit.mBody.Value().IsNull()) || 423 hasCopiedBody) { 424 // HEAD and GET are not allowed to have a body. 425 nsAutoCString method; 426 request->GetMethod(method); 427 // method is guaranteed to be uppercase due to step 14.2 above. 428 if (method.EqualsLiteral("HEAD") || method.EqualsLiteral("GET")) { 429 aRv.ThrowTypeError("HEAD or GET Request cannot have a body."); 430 return nullptr; 431 } 432 } 433 434 if (aInit.mBody.WasPassed()) { 435 const Nullable<fetch::OwningBodyInit>& bodyInitNullable = 436 aInit.mBody.Value(); 437 if (!bodyInitNullable.IsNull()) { 438 const fetch::OwningBodyInit& bodyInit = bodyInitNullable.Value(); 439 nsCOMPtr<nsIInputStream> stream; 440 nsAutoCString contentTypeWithCharset; 441 uint64_t contentLength = 0; 442 aRv = ExtractByteStreamFromBody(bodyInit, getter_AddRefs(stream), 443 contentTypeWithCharset, contentLength); 444 if (NS_WARN_IF(aRv.Failed())) { 445 return nullptr; 446 } 447 448 nsCOMPtr<nsIInputStream> temporaryBody = stream; 449 450 if (!contentTypeWithCharset.IsVoid() && 451 !requestHeaders->Has("Content-Type"_ns, aRv)) { 452 requestHeaders->Append("Content-Type"_ns, contentTypeWithCharset, aRv); 453 } 454 455 if (NS_WARN_IF(aRv.Failed())) { 456 return nullptr; 457 } 458 459 if (hasCopiedBody) { 460 request->SetBody(nullptr, 0); 461 } 462 463 request->SetBody(temporaryBody, contentLength); 464 } 465 } 466 467 auto domRequest = 468 MakeSafeRefPtr<Request>(aGlobal, std::move(request), signal); 469 470 if (aInput.IsRequest() && !bodyFromInit) { 471 RefPtr<Request> inputReq = &aInput.GetAsRequest(); 472 nsCOMPtr<nsIInputStream> body; 473 inputReq->GetBody(getter_AddRefs(body)); 474 if (body) { 475 inputReq->SetBody(nullptr, 0); 476 inputReq->SetBodyUsed(aCx, aRv); 477 if (NS_WARN_IF(aRv.Failed())) { 478 return nullptr; 479 } 480 } 481 } 482 return domRequest; 483 } 484 485 SafeRefPtr<Request> Request::Clone(ErrorResult& aRv) { 486 if (BodyUsed()) { 487 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); 488 return nullptr; 489 } 490 491 SafeRefPtr<InternalRequest> ir = mRequest->Clone(); 492 if (!ir) { 493 aRv.Throw(NS_ERROR_FAILURE); 494 return nullptr; 495 } 496 497 return MakeSafeRefPtr<Request>(mOwner, std::move(ir), GetOrCreateSignal()); 498 } 499 500 Headers* Request::Headers_() { 501 if (!mHeaders) { 502 mHeaders = new Headers(mOwner, mRequest->Headers()); 503 } 504 505 return mHeaders; 506 } 507 508 AbortSignal* Request::GetOrCreateSignal() { 509 if (!mSignal) { 510 mSignal = AbortSignal::Create(mOwner, SignalAborted::No, 511 JS::UndefinedHandleValue); 512 } 513 514 return mSignal; 515 } 516 517 AbortSignalImpl* Request::GetSignalImpl() const { return mSignal; } 518 519 AbortSignalImpl* Request::GetSignalImplToConsumeBody() const { 520 // This is a hack, see Response::GetSignalImplToConsumeBody. 521 return nullptr; 522 } 523 524 } // namespace mozilla::dom