Use ordinal comparison for known header values (#64702)
authorMiha Zupan <mihazupan.zupan1@gmail.com>
Thu, 3 Feb 2022 21:33:02 +0000 (13:33 -0800)
committerGitHub <noreply@github.com>
Thu, 3 Feb 2022 21:33:02 +0000 (13:33 -0800)
* Use ordinal comparison for known header values

* Match 'text/html; charset=UTF-8' again

src/libraries/System.Net.Http/src/System/Net/Http/Headers/HeaderDescriptor.cs
src/libraries/System.Net.Http/tests/UnitTests/Headers/KnownHeadersTest.cs

index 25c7577..d04c9f4 100644 (file)
@@ -143,7 +143,7 @@ namespace System.Net.Http.Headers
                 {
                     for (int i = 0; i < knownValues.Length; i++)
                     {
-                        if (ByteArrayHelpers.EqualsOrdinalAsciiIgnoreCase(knownValues[i], headerValue))
+                        if (ByteArrayHelpers.EqualsOrdinalAscii(knownValues[i], headerValue))
                         {
                             return knownValues[i];
                         }
@@ -177,45 +177,45 @@ namespace System.Net.Http.Headers
             switch (contentTypeValue.Length)
             {
                 case 8:
-                    switch (contentTypeValue[7] | 0x20)
+                    switch (contentTypeValue[7])
                     {
-                        case 'l': candidate = "text/xml"; break; // text/xm[l]
-                        case 's': candidate = "text/css"; break; // text/cs[s]
-                        case 'v': candidate = "text/csv"; break; // text/cs[v]
+                        case (byte)'l': candidate = "text/xml"; break; // text/xm[l]
+                        case (byte)'s': candidate = "text/css"; break; // text/cs[s]
+                        case (byte)'v': candidate = "text/csv"; break; // text/cs[v]
                     }
                     break;
 
                 case 9:
-                    switch (contentTypeValue[6] | 0x20)
+                    switch (contentTypeValue[6])
                     {
-                        case 'g': candidate = "image/gif"; break; // image/[g]if
-                        case 'p': candidate = "image/png"; break; // image/[p]ng
-                        case 't': candidate = "text/html"; break; // text/h[t]ml
+                        case (byte)'g': candidate = "image/gif"; break; // image/[g]if
+                        case (byte)'p': candidate = "image/png"; break; // image/[p]ng
+                        case (byte)'t': candidate = "text/html"; break; // text/h[t]ml
                     }
                     break;
 
                 case 10:
-                    switch (contentTypeValue[0] | 0x20)
+                    switch (contentTypeValue[0])
                     {
-                        case 't': candidate = "text/plain"; break; // [t]ext/plain
-                        case 'i': candidate = "image/jpeg"; break; // [i]mage/jpeg
+                        case (byte)'t': candidate = "text/plain"; break; // [t]ext/plain
+                        case (byte)'i': candidate = "image/jpeg"; break; // [i]mage/jpeg
                     }
                     break;
 
                 case 15:
-                    switch (contentTypeValue[12] | 0x20)
+                    switch (contentTypeValue[12])
                     {
-                        case 'p': candidate = "application/pdf"; break; // application/[p]df
-                        case 'x': candidate = "application/xml"; break; // application/[x]ml
-                        case 'z': candidate = "application/zip"; break; // application/[z]ip
+                        case (byte)'p': candidate = "application/pdf"; break; // application/[p]df
+                        case (byte)'x': candidate = "application/xml"; break; // application/[x]ml
+                        case (byte)'z': candidate = "application/zip"; break; // application/[z]ip
                     }
                     break;
 
                 case 16:
-                    switch (contentTypeValue[12] | 0x20)
+                    switch (contentTypeValue[12])
                     {
-                        case 'g': candidate = "application/grpc"; break; // application/[g]rpc
-                        case 'j': candidate = "application/json"; break; // application/[j]son
+                        case (byte)'g': candidate = "application/grpc"; break; // application/[g]rpc
+                        case (byte)'j': candidate = "application/json"; break; // application/[j]son
                     }
                     break;
 
@@ -228,10 +228,11 @@ namespace System.Net.Http.Headers
                     break;
 
                 case 24:
-                    switch (contentTypeValue[0] | 0x20)
+                    switch (contentTypeValue[19])
                     {
-                        case 'a': candidate = "application/octet-stream"; break; // application/octet-stream
-                        case 't': candidate = "text/html; charset=utf-8"; break; // text/html; charset=utf-8
+                        case (byte)'t': candidate = "application/octet-stream"; break; // application/octet-s[t]ream
+                        case (byte)'u': candidate = "text/html; charset=utf-8"; break; // text/html; charset=[u]tf-8
+                        case (byte)'U': candidate = "text/html; charset=UTF-8"; break; // text/html; charset=[U]TF-8
                     }
                     break;
 
@@ -250,7 +251,7 @@ namespace System.Net.Http.Headers
 
             Debug.Assert(candidate is null || candidate.Length == contentTypeValue.Length);
 
-            return candidate != null && ByteArrayHelpers.EqualsOrdinalAsciiIgnoreCase(candidate, contentTypeValue) ?
+            return candidate != null && ByteArrayHelpers.EqualsOrdinalAscii(candidate, contentTypeValue) ?
                 candidate :
                 null;
         }
index 78fada7..aac0f0f 100644 (file)
@@ -177,6 +177,7 @@ namespace System.Net.Http.Tests
         [InlineData("Content-Type", "application/javascript")]
         [InlineData("Content-Type", "application/octet-stream")]
         [InlineData("Content-Type", "text/html; charset=utf-8")]
+        [InlineData("Content-Type", "text/html; charset=UTF-8")]
         [InlineData("Content-Type", "text/plain; charset=utf-8")]
         [InlineData("Content-Type", "application/json; charset=utf-8")]
         [InlineData("Content-Type", "application/x-www-form-urlencoded")]
@@ -213,27 +214,46 @@ namespace System.Net.Http.Tests
         [InlineData("X-XSS-Protection", "1; mode=block")]
         public void GetKnownHeaderValue_Known_Found(string name, string value)
         {
-            foreach (string casedValue in new[] { value, value.ToUpperInvariant(), value.ToLowerInvariant() })
+            KnownHeader knownHeader = KnownHeaders.TryGetKnownHeader(name);
+            Assert.NotNull(knownHeader);
+
+            string v1 = knownHeader.Descriptor.GetHeaderValue(value.Select(c => (byte)c).ToArray(), valueEncoding: null);
+            Assert.NotNull(v1);
+            Assert.Equal(value, v1);
+
+            string v2 = knownHeader.Descriptor.GetHeaderValue(value.Select(c => (byte)c).ToArray(), valueEncoding: null);
+            Assert.Same(v1, v2);
+
+            if (TryChangeCasing(value, out string newValue)) // Doesn't make sense for values that are just numbers
             {
-                Validate(KnownHeaders.TryGetKnownHeader(name), casedValue);
+                GetKnownHeaderValue_Unknown_NotFound(name, newValue);
             }
 
-            static void Validate(KnownHeader knownHeader, string value)
+            static bool TryChangeCasing(string value, out string newValue)
             {
-                Assert.NotNull(knownHeader);
+                string upper = value.ToUpperInvariant();
+                if (upper != value)
+                {
+                    newValue = upper;
+                    return true;
+                }
 
-                string v1 = knownHeader.Descriptor.GetHeaderValue(value.Select(c => (byte)c).ToArray(), valueEncoding: null);
-                Assert.NotNull(v1);
-                Assert.Equal(value, v1, StringComparer.OrdinalIgnoreCase);
+                string lower = value.ToLowerInvariant();
+                if (lower != value)
+                {
+                    newValue = lower;
+                    return true;
+                }
 
-                string v2 = knownHeader.Descriptor.GetHeaderValue(value.Select(c => (byte)c).ToArray(), valueEncoding: null);
-                Assert.Same(v1, v2);
+                newValue = null;
+                return false;
             }
         }
 
         [Theory]
         [InlineData("Content-Type", "application/jsot")]
         [InlineData("Content-Type", "application/jsons")]
+        [InlineData("Transfer-Encoding", "foo")]
         public void GetKnownHeaderValue_Unknown_NotFound(string name, string value)
         {
             KnownHeader knownHeader = KnownHeaders.TryGetKnownHeader(name);