Bug 1539003 - update InternalHeaders get/set/append/delete to match the spec; r=hsivonen
authorThomas Wisniewski <twisniewski@mozilla.com>
Tue, 16 Apr 2019 06:34:05 +0000
changeset 469657 735492db6be1
parent 469656 ce0b0350b646
child 469658 f0c87ec1d694
push id35878
push userapavel@mozilla.com
push dateTue, 16 Apr 2019 15:43:40 +0000
treeherdermozilla-central@258af4e91151 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershsivonen
bugs1539003
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1539003 - update InternalHeaders get/set/append/delete to match the spec; r=hsivonen update Fetch InternalHeaders get/set/append/delete to match the spec Differential Revision: https://phabricator.services.mozilla.com/D27531
dom/fetch/InternalHeaders.cpp
dom/fetch/InternalHeaders.h
testing/web-platform/meta/fetch/api/headers/headers-no-cors.window.js.ini
--- a/dom/fetch/InternalHeaders.cpp
+++ b/dom/fetch/InternalHeaders.cpp
@@ -35,67 +35,183 @@ void InternalHeaders::ToIPC(nsTArray<Hea
   aGuard = mGuard;
 
   aIPCHeaders.Clear();
   for (Entry& entry : mList) {
     aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue));
   }
 }
 
+bool InternalHeaders::IsValidHeaderValue(const nsCString& aLowerName,
+                                         const nsCString& aNormalizedValue,
+                                         ErrorResult& aRv) {
+  // Steps 2 to 6 for ::Set() and ::Append() in the spec.
+
+  // Step 2
+  if (IsInvalidName(aLowerName, aRv) || IsInvalidValue(aNormalizedValue, aRv)) {
+    return false;
+  }
+
+  // Step 3
+  if (IsImmutable(aRv)) {
+    return false;
+  }
+
+  // Step 4
+  if (mGuard == HeadersGuardEnum::Request &&
+      IsForbiddenRequestHeader(aLowerName)) {
+    return false;
+  }
+
+  // Step 5
+  if (mGuard == HeadersGuardEnum::Request_no_cors) {
+    nsAutoCString tempValue;
+    Get(aLowerName, tempValue, aRv);
+
+    if (tempValue.IsVoid()) {
+      tempValue = aNormalizedValue;
+    } else {
+      tempValue.Append(", ");
+      tempValue.Append(aNormalizedValue);
+    }
+
+    if (!nsContentUtils::IsCORSSafelistedRequestHeader(aLowerName, tempValue)) {
+      return false;
+    }
+  }
+
+  // Step 6
+  else if (IsForbiddenResponseHeader(aLowerName)) {
+    return false;
+  }
+
+  return true;
+}
+
 void InternalHeaders::Append(const nsACString& aName, const nsACString& aValue,
                              ErrorResult& aRv) {
-  nsAutoCString lowerName;
-  ToLowerCase(aName, lowerName);
+  // Step 1
   nsAutoCString trimValue;
   NS_TrimHTTPWhitespace(aValue, trimValue);
 
-  if (IsInvalidMutableHeader(lowerName, trimValue, aRv)) {
+  // Steps 2 to 6
+  nsAutoCString lowerName;
+  ToLowerCase(aName, lowerName);
+  if (!IsValidHeaderValue(lowerName, trimValue, aRv)) {
     return;
   }
 
+  // Step 7
   nsAutoCString name(aName);
   ReuseExistingNameIfExists(name);
+  SetListDirty();
+  mList.AppendElement(Entry(name, trimValue));
 
-  SetListDirty();
+  // Step 8
+  if (mGuard == HeadersGuardEnum::Request_no_cors) {
+    RemovePrivilegedNoCorsRequestHeaders();
+  }
+}
+
+void InternalHeaders::RemovePrivilegedNoCorsRequestHeaders() {
+  bool dirty = false;
+
+  // remove in reverse order to minimize copying
+  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
+    if (IsPrivilegedNoCorsRequestHeaderName(mList[i].mName)) {
+      mList.RemoveElementAt(i);
+      dirty = true;
+    }
+  }
 
-  mList.AppendElement(Entry(name, trimValue));
+  if (dirty) {
+    SetListDirty();
+  }
+}
+
+bool InternalHeaders::DeleteInternal(const nsCString& aLowerName,
+                                     ErrorResult& aRv) {
+  bool dirty = false;
+
+  // remove in reverse order to minimize copying
+  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
+    if (mList[i].mName.EqualsIgnoreCase(aLowerName.get())) {
+      mList.RemoveElementAt(i);
+      dirty = true;
+    }
+  }
+
+  if (dirty) {
+    SetListDirty();
+  }
+
+  return dirty;
 }
 
 void InternalHeaders::Delete(const nsACString& aName, ErrorResult& aRv) {
   nsAutoCString lowerName;
   ToLowerCase(aName, lowerName);
 
-  if (IsInvalidMutableHeader(lowerName, aRv)) {
+  // Step 1
+  if (IsInvalidName(lowerName, aRv)) {
+    return;
+  }
+
+  // Step 2
+  if (IsImmutable(aRv)) {
+    return;
+  }
+
+  // Step 3
+  if (IsForbiddenRequestHeader(lowerName)) {
     return;
   }
 
-  SetListDirty();
+  // Step 4
+  if (mGuard == HeadersGuardEnum::Request_no_cors &&
+      !IsNoCorsSafelistedRequestHeaderName(lowerName) &&
+      !IsPrivilegedNoCorsRequestHeaderName(lowerName)) {
+    return;
+  }
 
-  // remove in reverse order to minimize copying
-  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
-    if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
-      mList.RemoveElementAt(i);
-    }
+  // Step 5
+  if (IsForbiddenResponseHeader(lowerName)) {
+    return;
+  }
+
+  // Steps 6 and 7
+  if (!DeleteInternal(lowerName, aRv)) {
+    return;
+  }
+
+  // Step 8
+  if (mGuard == HeadersGuardEnum::Request_no_cors) {
+    RemovePrivilegedNoCorsRequestHeaders();
   }
 }
 
 void InternalHeaders::Get(const nsACString& aName, nsACString& aValue,
                           ErrorResult& aRv) const {
   nsAutoCString lowerName;
   ToLowerCase(aName, lowerName);
 
   if (IsInvalidName(lowerName, aRv)) {
     return;
   }
 
+  GetInternal(lowerName, aValue, aRv);
+}
+
+void InternalHeaders::GetInternal(const nsCString& aLowerName,
+                                  nsACString& aValue, ErrorResult& aRv) const {
   const char* delimiter = ", ";
   bool firstValueFound = false;
 
   for (uint32_t i = 0; i < mList.Length(); ++i) {
-    if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
+    if (mList[i].mName.EqualsIgnoreCase(aLowerName.get())) {
       if (firstValueFound) {
         aValue += delimiter;
       }
       aValue += mList[i].mValue;
       firstValueFound = true;
     }
   }
 
@@ -138,25 +254,28 @@ bool InternalHeaders::Has(const nsACStri
       return true;
     }
   }
   return false;
 }
 
 void InternalHeaders::Set(const nsACString& aName, const nsACString& aValue,
                           ErrorResult& aRv) {
-  nsAutoCString lowerName;
-  ToLowerCase(aName, lowerName);
+  // Step 1
   nsAutoCString trimValue;
   NS_TrimHTTPWhitespace(aValue, trimValue);
 
-  if (IsInvalidMutableHeader(lowerName, trimValue, aRv)) {
+  // Steps 2 to 6
+  nsAutoCString lowerName;
+  ToLowerCase(aName, lowerName);
+  if (!IsValidHeaderValue(lowerName, trimValue, aRv)) {
     return;
   }
 
+  // Step 7
   SetListDirty();
 
   int32_t firstIndex = INT32_MAX;
 
   // remove in reverse order to minimize copying
   for (int32_t i = mList.Length() - 1; i >= 0; --i) {
     if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
       firstIndex = std::min(firstIndex, i);
@@ -166,32 +285,52 @@ void InternalHeaders::Set(const nsACStri
 
   if (firstIndex < INT32_MAX) {
     Entry* entry = mList.InsertElementAt(firstIndex);
     entry->mName = aName;
     entry->mValue = trimValue;
   } else {
     mList.AppendElement(Entry(aName, trimValue));
   }
+
+  // Step 8
+  if (mGuard == HeadersGuardEnum::Request_no_cors) {
+    RemovePrivilegedNoCorsRequestHeaders();
+  }
 }
 
 void InternalHeaders::Clear() {
   SetListDirty();
   mList.Clear();
 }
 
 void InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv) {
   // The guard is only checked during ::Set() and ::Append() in the spec.  It
   // does not require revalidating headers already set.
   mGuard = aGuard;
 }
 
 InternalHeaders::~InternalHeaders() {}
 
 // static
+bool InternalHeaders::IsNoCorsSafelistedRequestHeaderName(
+    const nsCString& aName) {
+  return aName.EqualsIgnoreCase("accept") ||
+         aName.EqualsIgnoreCase("accept-language") ||
+         aName.EqualsIgnoreCase("content-language") ||
+         aName.EqualsIgnoreCase("content-type");
+}
+
+// static
+bool InternalHeaders::IsPrivilegedNoCorsRequestHeaderName(
+    const nsCString& aName) {
+  return aName.EqualsIgnoreCase("range");
+}
+
+// static
 bool InternalHeaders::IsSimpleHeader(const nsCString& aName,
                                      const nsACString& aValue) {
   if (aValue.Length() > 128) {
     return false;
   }
   // Note, we must allow a null content-type value here to support
   // get("content-type"), but the IsInvalidValue() check will prevent null
   // from being set or appended.
@@ -371,18 +510,18 @@ already_AddRefed<InternalHeaders> Intern
 
 // static
 already_AddRefed<InternalHeaders> InternalHeaders::CORSHeaders(
     InternalHeaders* aHeaders) {
   RefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard);
   ErrorResult result;
 
   nsAutoCString acExposedNames;
-  aHeaders->GetFirst(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"),
-                     acExposedNames, result);
+  aHeaders->Get(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"),
+                acExposedNames, result);
   MOZ_ASSERT(!result.Failed());
 
   AutoTArray<nsCString, 5> exposeNamesArray;
   nsCCharSeparatedTokenizer exposeTokens(acExposedNames, ',');
   while (exposeTokens.hasMoreTokens()) {
     const nsDependentCSubstring& token = exposeTokens.nextToken();
     if (token.IsEmpty()) {
       continue;
--- a/dom/fetch/InternalHeaders.h
+++ b/dom/fetch/InternalHeaders.h
@@ -119,16 +119,18 @@ class InternalHeaders final {
 
   void GetUnsafeHeaders(nsTArray<nsCString>& aNames) const;
 
  private:
   virtual ~InternalHeaders();
 
   static bool IsInvalidName(const nsACString& aName, ErrorResult& aRv);
   static bool IsInvalidValue(const nsACString& aValue, ErrorResult& aRv);
+  bool IsValidHeaderValue(const nsCString& aLowerName,
+                          const nsCString& aNormalizedValue, ErrorResult& aRv);
   bool IsImmutable(ErrorResult& aRv) const;
   bool IsForbiddenRequestHeader(const nsCString& aName) const;
   bool IsForbiddenRequestNoCorsHeader(const nsCString& aName) const;
   bool IsForbiddenRequestNoCorsHeader(const nsCString& aName,
                                       const nsACString& aValue) const;
   bool IsForbiddenResponseHeader(const nsCString& aName) const;
 
   bool IsInvalidMutableHeader(const nsCString& aName, ErrorResult& aRv) const {
@@ -142,16 +144,27 @@ class InternalHeaders final {
            IsForbiddenRequestNoCorsHeader(aName, aValue) ||
            IsForbiddenResponseHeader(aName);
   }
 
   // This method updates the passed name to match the capitalization of a header
   // with the same name (ignoring case, per the spec).
   void ReuseExistingNameIfExists(nsCString& aName) const;
 
+  void RemovePrivilegedNoCorsRequestHeaders();
+
+  void GetInternal(const nsCString& aLowerName, nsACString& aValue,
+                   ErrorResult& aRv) const;
+
+  bool DeleteInternal(const nsCString& aLowerName, ErrorResult& aRv);
+
+  static bool IsNoCorsSafelistedRequestHeaderName(const nsCString& aName);
+
+  static bool IsPrivilegedNoCorsRequestHeaderName(const nsCString& aName);
+
   static bool IsSimpleHeader(const nsCString& aName, const nsACString& aValue);
 
   static bool IsRevalidationHeader(const nsCString& aName);
 
   void MaybeSortList();
   void SetListDirty();
 };
 
deleted file mode 100644
--- a/testing/web-platform/meta/fetch/api/headers/headers-no-cors.window.js.ini
+++ /dev/null
@@ -1,22 +0,0 @@
-[headers-no-cors.window.html]
-  ["no-cors" Headers object cannot have content-type set to text/plain;ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, text/plain]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have accept set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have accept-language set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have content-language set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have content-language set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have accept-language set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have accept set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss]
-    expected: FAIL
-