InternalHeaders.cpp (17245B)
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 "mozilla/dom/InternalHeaders.h" 8 9 #include "FetchUtil.h" 10 #include "mozilla/ErrorResult.h" 11 #include "mozilla/dom/FetchTypes.h" 12 #include "nsCharSeparatedTokenizer.h" 13 #include "nsContentUtils.h" 14 #include "nsIHttpChannel.h" 15 #include "nsIHttpHeaderVisitor.h" 16 #include "nsNetUtil.h" 17 #include "nsReadableUtils.h" 18 19 namespace mozilla::dom { 20 21 InternalHeaders::InternalHeaders(nsTArray<Entry>&& aHeaders, 22 HeadersGuardEnum aGuard) 23 : mGuard(aGuard), mList(std::move(aHeaders)), mListDirty(true) {} 24 25 InternalHeaders::InternalHeaders( 26 const nsTArray<HeadersEntry>& aHeadersEntryList, HeadersGuardEnum aGuard) 27 : mGuard(aGuard), mListDirty(true) { 28 for (const HeadersEntry& headersEntry : aHeadersEntryList) { 29 mList.AppendElement(Entry(headersEntry.name(), headersEntry.value())); 30 } 31 } 32 33 void InternalHeaders::ToIPC(nsTArray<HeadersEntry>& aIPCHeaders, 34 HeadersGuardEnum& aGuard) { 35 aGuard = mGuard; 36 37 aIPCHeaders.Clear(); 38 for (Entry& entry : mList) { 39 aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue)); 40 } 41 } 42 43 bool InternalHeaders::IsValidHeaderValue(const nsCString& aLowerName, 44 const nsCString& aNormalizedValue, 45 ErrorResult& aRv) { 46 // Steps 2 to 6 for ::Set() and ::Append() in the spec. 47 48 // Step 2 49 if (IsInvalidName(aLowerName, aRv) || IsInvalidValue(aNormalizedValue, aRv)) { 50 return false; 51 } 52 53 // Step 3 54 if (IsImmutable(aRv)) { 55 return false; 56 } 57 58 // Step 4 59 if (mGuard == HeadersGuardEnum::Request) { 60 if (IsForbiddenRequestHeader(aLowerName, aNormalizedValue)) { 61 return false; 62 } 63 } 64 // Step 5 65 if (mGuard == HeadersGuardEnum::Request_no_cors) { 66 nsAutoCString tempValue; 67 Get(aLowerName, tempValue, aRv); 68 69 if (tempValue.IsVoid()) { 70 tempValue = aNormalizedValue; 71 } else { 72 tempValue.Append(", "); 73 tempValue.Append(aNormalizedValue); 74 } 75 76 if (!nsContentUtils::IsCORSSafelistedRequestHeader(aLowerName, tempValue)) { 77 return false; 78 } 79 } 80 81 // Step 6 82 else if (IsForbiddenResponseHeader(aLowerName)) { 83 return false; 84 } 85 86 return true; 87 } 88 89 void InternalHeaders::Append(const nsACString& aName, const nsACString& aValue, 90 ErrorResult& aRv) { 91 // Step 1 92 nsAutoCString trimValue; 93 NS_TrimHTTPWhitespace(aValue, trimValue); 94 95 // Steps 2 to 6 96 nsAutoCString lowerName; 97 ToLowerCase(aName, lowerName); 98 if (!IsValidHeaderValue(lowerName, trimValue, aRv)) { 99 return; 100 } 101 102 // Step 7 103 nsAutoCString name(aName); 104 ReuseExistingNameIfExists(name); 105 SetListDirty(); 106 mList.AppendElement(Entry(name, trimValue)); 107 108 // Step 8 109 if (mGuard == HeadersGuardEnum::Request_no_cors) { 110 RemovePrivilegedNoCorsRequestHeaders(); 111 } 112 } 113 114 void InternalHeaders::RemovePrivilegedNoCorsRequestHeaders() { 115 bool dirty = false; 116 117 // remove in reverse order to minimize copying 118 for (int32_t i = mList.Length() - 1; i >= 0; --i) { 119 if (IsPrivilegedNoCorsRequestHeaderName(mList[i].mName)) { 120 mList.RemoveElementAt(i); 121 dirty = true; 122 } 123 } 124 125 if (dirty) { 126 SetListDirty(); 127 } 128 } 129 130 bool InternalHeaders::DeleteInternal(const nsCString& aLowerName, 131 ErrorResult& aRv) { 132 bool dirty = false; 133 134 // remove in reverse order to minimize copying 135 for (int32_t i = mList.Length() - 1; i >= 0; --i) { 136 if (mList[i].mName.EqualsIgnoreCase(aLowerName.get())) { 137 mList.RemoveElementAt(i); 138 dirty = true; 139 } 140 } 141 142 if (dirty) { 143 SetListDirty(); 144 } 145 146 return dirty; 147 } 148 149 void InternalHeaders::Delete(const nsACString& aName, ErrorResult& aRv) { 150 // See https://fetch.spec.whatwg.org/#dom-headers-delete 151 nsAutoCString lowerName; 152 ToLowerCase(aName, lowerName); 153 154 // Step 1 155 if (IsInvalidName(lowerName, aRv)) { 156 return; 157 } 158 159 if (IsImmutable(aRv)) { 160 return; 161 } 162 163 nsAutoCString value; 164 GetInternal(lowerName, value, aRv); 165 if (IsForbiddenRequestHeader(lowerName, value)) { 166 return; 167 } 168 169 // Step 2 170 if (mGuard == HeadersGuardEnum::Request_no_cors && 171 !IsNoCorsSafelistedRequestHeaderName(lowerName) && 172 !IsPrivilegedNoCorsRequestHeaderName(lowerName)) { 173 return; 174 } 175 176 if (IsForbiddenResponseHeader(lowerName)) { 177 return; 178 } 179 180 // Steps 3, 4, and 5 181 if (!DeleteInternal(lowerName, aRv)) { 182 return; 183 } 184 185 // Step 6 186 if (mGuard == HeadersGuardEnum::Request_no_cors) { 187 RemovePrivilegedNoCorsRequestHeaders(); 188 } 189 } 190 191 void InternalHeaders::Get(const nsACString& aName, nsACString& aValue, 192 ErrorResult& aRv) const { 193 nsAutoCString lowerName; 194 ToLowerCase(aName, lowerName); 195 196 if (IsInvalidName(lowerName, aRv)) { 197 return; 198 } 199 200 GetInternal(lowerName, aValue, aRv); 201 } 202 203 void InternalHeaders::GetInternal(const nsCString& aLowerName, 204 nsACString& aValue, ErrorResult& aRv) const { 205 const char* delimiter = ", "; 206 bool firstValueFound = false; 207 208 for (uint32_t i = 0; i < mList.Length(); ++i) { 209 if (mList[i].mName.EqualsIgnoreCase(aLowerName.get())) { 210 if (firstValueFound) { 211 aValue += delimiter; 212 } 213 aValue += mList[i].mValue; 214 firstValueFound = true; 215 } 216 } 217 218 // No value found, so return null to content 219 if (!firstValueFound) { 220 aValue.SetIsVoid(true); 221 } 222 } 223 224 void InternalHeaders::GetSetCookie(nsTArray<nsCString>& aValues) const { 225 for (uint32_t i = 0; i < mList.Length(); ++i) { 226 if (mList[i].mName.EqualsIgnoreCase("Set-Cookie")) { 227 aValues.AppendElement(mList[i].mValue); 228 } 229 } 230 } 231 232 void InternalHeaders::GetFirst(const nsACString& aName, nsACString& aValue, 233 ErrorResult& aRv) const { 234 nsAutoCString lowerName; 235 ToLowerCase(aName, lowerName); 236 237 if (IsInvalidName(lowerName, aRv)) { 238 return; 239 } 240 241 for (uint32_t i = 0; i < mList.Length(); ++i) { 242 if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) { 243 aValue = mList[i].mValue; 244 return; 245 } 246 } 247 248 // No value found, so return null to content 249 aValue.SetIsVoid(true); 250 } 251 252 bool InternalHeaders::Has(const nsACString& aName, ErrorResult& aRv) const { 253 nsAutoCString lowerName; 254 ToLowerCase(aName, lowerName); 255 256 if (IsInvalidName(lowerName, aRv)) { 257 return false; 258 } 259 260 for (uint32_t i = 0; i < mList.Length(); ++i) { 261 if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) { 262 return true; 263 } 264 } 265 return false; 266 } 267 268 void InternalHeaders::Set(const nsACString& aName, const nsACString& aValue, 269 ErrorResult& aRv) { 270 // Step 1 271 nsAutoCString trimValue; 272 NS_TrimHTTPWhitespace(aValue, trimValue); 273 274 // Steps 2 to 6 275 nsAutoCString lowerName; 276 ToLowerCase(aName, lowerName); 277 if (!IsValidHeaderValue(lowerName, trimValue, aRv)) { 278 return; 279 } 280 281 // Step 7 282 SetListDirty(); 283 284 int32_t firstIndex = INT32_MAX; 285 286 // remove in reverse order to minimize copying 287 for (int32_t i = mList.Length() - 1; i >= 0; --i) { 288 if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) { 289 firstIndex = std::min(firstIndex, i); 290 mList.RemoveElementAt(i); 291 } 292 } 293 294 if (firstIndex < INT32_MAX) { 295 Entry* entry = mList.InsertElementAt(firstIndex); 296 entry->mName = aName; 297 entry->mValue = trimValue; 298 } else { 299 mList.AppendElement(Entry(aName, trimValue)); 300 } 301 302 // Step 8 303 if (mGuard == HeadersGuardEnum::Request_no_cors) { 304 RemovePrivilegedNoCorsRequestHeaders(); 305 } 306 } 307 308 void InternalHeaders::Clear() { 309 SetListDirty(); 310 mList.Clear(); 311 } 312 313 void InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv) { 314 // The guard is only checked during ::Set() and ::Append() in the spec. It 315 // does not require revalidating headers already set. 316 mGuard = aGuard; 317 } 318 319 InternalHeaders::~InternalHeaders() = default; 320 321 // static 322 bool InternalHeaders::IsNoCorsSafelistedRequestHeaderName( 323 const nsCString& aName) { 324 return aName.EqualsIgnoreCase("accept") || 325 aName.EqualsIgnoreCase("accept-language") || 326 aName.EqualsIgnoreCase("content-language") || 327 aName.EqualsIgnoreCase("content-type"); 328 } 329 330 // static 331 bool InternalHeaders::IsPrivilegedNoCorsRequestHeaderName( 332 const nsCString& aName) { 333 return aName.EqualsIgnoreCase("range"); 334 } 335 336 // static 337 bool InternalHeaders::IsRevalidationHeader(const nsCString& aName) { 338 return aName.EqualsIgnoreCase("if-modified-since") || 339 aName.EqualsIgnoreCase("if-none-match") || 340 aName.EqualsIgnoreCase("if-unmodified-since") || 341 aName.EqualsIgnoreCase("if-match") || 342 aName.EqualsIgnoreCase("if-range"); 343 } 344 345 // static 346 bool InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv) { 347 if (!NS_IsValidHTTPToken(aName)) { 348 aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(aName); 349 return true; 350 } 351 352 return false; 353 } 354 355 // static 356 bool InternalHeaders::IsInvalidValue(const nsACString& aValue, 357 ErrorResult& aRv) { 358 if (!NS_IsReasonableHTTPHeaderValue(aValue)) { 359 aRv.ThrowTypeError<MSG_INVALID_HEADER_VALUE>(aValue); 360 return true; 361 } 362 return false; 363 } 364 365 bool InternalHeaders::IsImmutable(ErrorResult& aRv) const { 366 if (mGuard == HeadersGuardEnum::Immutable) { 367 aRv.ThrowTypeError("Headers are immutable and cannot be modified."); 368 return true; 369 } 370 return false; 371 } 372 373 bool InternalHeaders::IsForbiddenRequestHeader(const nsCString& aName, 374 const nsACString& aValue) const { 375 return mGuard == HeadersGuardEnum::Request && 376 nsContentUtils::IsForbiddenRequestHeader(aName, aValue); 377 } 378 379 bool InternalHeaders::IsForbiddenRequestNoCorsHeader( 380 const nsCString& aName) const { 381 return mGuard == HeadersGuardEnum::Request_no_cors && 382 !nsContentUtils::IsCORSSafelistedRequestHeader(aName, ""_ns); 383 } 384 385 bool InternalHeaders::IsForbiddenRequestNoCorsHeader( 386 const nsCString& aName, const nsACString& aValue) const { 387 return mGuard == HeadersGuardEnum::Request_no_cors && 388 !nsContentUtils::IsCORSSafelistedRequestHeader(aName, aValue); 389 } 390 391 bool InternalHeaders::IsForbiddenResponseHeader(const nsCString& aName) const { 392 return mGuard == HeadersGuardEnum::Response && 393 nsContentUtils::IsForbiddenResponseHeader(aName); 394 } 395 396 void InternalHeaders::Fill(const InternalHeaders& aInit, ErrorResult& aRv) { 397 const nsTArray<Entry>& list = aInit.mList; 398 for (uint32_t i = 0; i < list.Length() && !aRv.Failed(); ++i) { 399 const Entry& entry = list[i]; 400 Append(entry.mName, entry.mValue, aRv); 401 } 402 } 403 404 void InternalHeaders::Fill(const Sequence<Sequence<nsCString>>& aInit, 405 ErrorResult& aRv) { 406 for (uint32_t i = 0; i < aInit.Length() && !aRv.Failed(); ++i) { 407 const Sequence<nsCString>& tuple = aInit[i]; 408 if (tuple.Length() != 2) { 409 aRv.ThrowTypeError( 410 "Headers require name/value tuples when being initialized by a " 411 "sequence."); 412 return; 413 } 414 Append(tuple[0], tuple[1], aRv); 415 } 416 } 417 418 void InternalHeaders::Fill(const Record<nsCString, nsCString>& aInit, 419 ErrorResult& aRv) { 420 for (auto& entry : aInit.Entries()) { 421 Append(entry.mKey, entry.mValue, aRv); 422 if (aRv.Failed()) { 423 return; 424 } 425 } 426 } 427 428 namespace { 429 430 class FillHeaders final : public nsIHttpHeaderVisitor { 431 RefPtr<InternalHeaders> mInternalHeaders; 432 433 ~FillHeaders() = default; 434 435 public: 436 NS_DECL_ISUPPORTS 437 438 explicit FillHeaders(InternalHeaders* aInternalHeaders) 439 : mInternalHeaders(aInternalHeaders) { 440 MOZ_DIAGNOSTIC_ASSERT(mInternalHeaders); 441 } 442 443 NS_IMETHOD 444 VisitHeader(const nsACString& aHeader, const nsACString& aValue) override { 445 mInternalHeaders->Append(aHeader, aValue, IgnoreErrors()); 446 return NS_OK; 447 } 448 }; 449 450 NS_IMPL_ISUPPORTS(FillHeaders, nsIHttpHeaderVisitor) 451 452 } // namespace 453 454 void InternalHeaders::FillResponseHeaders(nsIRequest* aRequest) { 455 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); 456 if (!httpChannel) { 457 return; 458 } 459 460 RefPtr<FillHeaders> visitor = new FillHeaders(this); 461 nsresult rv = httpChannel->VisitResponseHeaders(visitor); 462 if (NS_FAILED(rv)) { 463 NS_WARNING("failed to fill headers"); 464 } 465 } 466 467 bool InternalHeaders::HasOnlySimpleHeaders() const { 468 for (uint32_t i = 0; i < mList.Length(); ++i) { 469 if (!nsContentUtils::IsCORSSafelistedRequestHeader(mList[i].mName, 470 mList[i].mValue)) { 471 return false; 472 } 473 } 474 475 return true; 476 } 477 478 bool InternalHeaders::HasRevalidationHeaders() const { 479 for (uint32_t i = 0; i < mList.Length(); ++i) { 480 if (IsRevalidationHeader(mList[i].mName)) { 481 return true; 482 } 483 } 484 485 return false; 486 } 487 488 // static 489 already_AddRefed<InternalHeaders> InternalHeaders::BasicHeaders( 490 InternalHeaders* aHeaders) { 491 RefPtr<InternalHeaders> basic = new InternalHeaders(*aHeaders); 492 ErrorResult result; 493 // The Set-Cookie headers cannot be invalid mutable headers, so the Delete 494 // must succeed. 495 basic->Delete("Set-Cookie"_ns, result); 496 MOZ_ASSERT(!result.Failed()); 497 basic->Delete("Set-Cookie2"_ns, result); 498 MOZ_ASSERT(!result.Failed()); 499 return basic.forget(); 500 } 501 502 // static 503 already_AddRefed<InternalHeaders> InternalHeaders::CORSHeaders( 504 InternalHeaders* aHeaders, RequestCredentials aCredentialsMode) { 505 RefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard); 506 ErrorResult result; 507 508 nsAutoCString acExposedNames; 509 aHeaders->Get("Access-Control-Expose-Headers"_ns, acExposedNames, result); 510 MOZ_ASSERT(!result.Failed()); 511 512 bool allowAllHeaders = false; 513 AutoTArray<nsCString, 5> exposeNamesArray; 514 for (const nsACString& token : 515 nsCCharSeparatedTokenizer(acExposedNames, ',').ToRange()) { 516 if (token.IsEmpty()) { 517 continue; 518 } 519 520 if (!NS_IsValidHTTPToken(token)) { 521 NS_WARNING( 522 "Got invalid HTTP token in Access-Control-Expose-Headers. Header " 523 "value is:"); 524 NS_WARNING(acExposedNames.get()); 525 exposeNamesArray.Clear(); 526 break; 527 } 528 529 if (token.EqualsLiteral("*") && 530 aCredentialsMode != RequestCredentials::Include) { 531 allowAllHeaders = true; 532 } 533 534 exposeNamesArray.AppendElement(token); 535 } 536 537 nsCaseInsensitiveCStringArrayComparator comp; 538 for (uint32_t i = 0; i < aHeaders->mList.Length(); ++i) { 539 const Entry& entry = aHeaders->mList[i]; 540 if (allowAllHeaders) { 541 cors->Append(entry.mName, entry.mValue, result); 542 MOZ_ASSERT(!result.Failed()); 543 } else if (entry.mName.EqualsIgnoreCase("cache-control") || 544 entry.mName.EqualsIgnoreCase("content-language") || 545 entry.mName.EqualsIgnoreCase("content-type") || 546 entry.mName.EqualsIgnoreCase("content-length") || 547 entry.mName.EqualsIgnoreCase("expires") || 548 entry.mName.EqualsIgnoreCase("last-modified") || 549 entry.mName.EqualsIgnoreCase("pragma") || 550 exposeNamesArray.Contains(entry.mName, comp)) { 551 cors->Append(entry.mName, entry.mValue, result); 552 MOZ_ASSERT(!result.Failed()); 553 } 554 } 555 556 return cors.forget(); 557 } 558 559 void InternalHeaders::GetEntries( 560 nsTArray<InternalHeaders::Entry>& aEntries) const { 561 MOZ_ASSERT(aEntries.IsEmpty()); 562 aEntries.AppendElements(mList); 563 } 564 565 void InternalHeaders::GetUnsafeHeaders(nsTArray<nsCString>& aNames) const { 566 MOZ_ASSERT(aNames.IsEmpty()); 567 for (uint32_t i = 0; i < mList.Length(); ++i) { 568 const Entry& header = mList[i]; 569 if (!nsContentUtils::IsCORSSafelistedRequestHeader(header.mName, 570 header.mValue)) { 571 aNames.AppendElement(header.mName); 572 } 573 } 574 } 575 576 void InternalHeaders::MaybeSortList() { 577 class Comparator { 578 public: 579 bool Equals(const Entry& aA, const Entry& aB) const { 580 return aA.mName == aB.mName; 581 } 582 583 bool LessThan(const Entry& aA, const Entry& aB) const { 584 return aA.mName < aB.mName; 585 } 586 }; 587 588 if (!mListDirty) { 589 return; 590 } 591 592 mListDirty = false; 593 594 Comparator comparator; 595 596 mSortedList.Clear(); 597 for (const Entry& entry : mList) { 598 bool found = false; 599 600 // We combine every header but Set-Cookie. 601 if (!entry.mName.EqualsIgnoreCase("Set-Cookie")) { 602 for (Entry& sortedEntry : mSortedList) { 603 if (sortedEntry.mName.EqualsIgnoreCase(entry.mName.get())) { 604 sortedEntry.mValue += ", "; 605 sortedEntry.mValue += entry.mValue; 606 found = true; 607 break; 608 } 609 } 610 } 611 612 if (!found) { 613 Entry newEntry = entry; 614 ToLowerCase(newEntry.mName); 615 mSortedList.InsertElementSorted(newEntry, comparator); 616 } 617 } 618 } 619 620 void InternalHeaders::SetListDirty() { 621 mSortedList.Clear(); 622 mListDirty = true; 623 } 624 625 void InternalHeaders::ReuseExistingNameIfExists(nsCString& aName) const { 626 for (const Entry& entry : mList) { 627 if (entry.mName.EqualsIgnoreCase(aName.get())) { 628 aName = entry.mName; 629 break; 630 } 631 } 632 } 633 634 } // namespace mozilla::dom