Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit fc29a3f

Browse files
committed
Porting #23591 to release 2.0 branch.
Fixes #23582, fixes #23574
1 parent 8e134e9 commit fc29a3f

File tree

2 files changed

+180
-44
lines changed

2 files changed

+180
-44
lines changed

src/System.Web.HttpUtility/src/System/Web/HttpUtility.cs

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -44,47 +44,37 @@ public static NameValueCollection ParseQueryString(string query)
4444
public static NameValueCollection ParseQueryString(string query, Encoding encoding)
4545
{
4646
if (query == null)
47+
{
4748
throw new ArgumentNullException(nameof(query));
49+
}
4850

4951
if (encoding == null)
52+
{
5053
throw new ArgumentNullException(nameof(encoding));
54+
}
5155

52-
if ((query.Length > 0) && (query[0] == '?'))
53-
query = query.Substring(1);
54-
55-
var result = new HttpQSCollection();
56-
ParseQueryString(query, encoding, result);
57-
return result;
58-
}
56+
HttpQSCollection result = new HttpQSCollection();
57+
int queryLength = query.Length;
58+
int namePos = queryLength > 0 && query[0] == '?' ? 1 : 0;
59+
if (queryLength == namePos)
60+
{
61+
return result;
62+
}
5963

60-
private static void ParseQueryString(string query, Encoding encoding, NameValueCollection result)
61-
{
62-
if (query.Length == 0)
63-
return;
64-
65-
var decoded = HtmlDecode(query);
66-
var decodedLength = decoded.Length;
67-
var namePos = 0;
68-
var first = true;
69-
while (namePos <= decodedLength)
64+
while (namePos <= queryLength)
7065
{
7166
int valuePos = -1, valueEnd = -1;
72-
for (var q = namePos; q < decodedLength; q++)
73-
if ((valuePos == -1) && (decoded[q] == '='))
67+
for (int q = namePos; q < queryLength; q++)
68+
{
69+
if (valuePos == -1 && query[q] == '=')
7470
{
7571
valuePos = q + 1;
7672
}
77-
else if (decoded[q] == '&')
73+
else if (query[q] == '&')
7874
{
7975
valueEnd = q;
8076
break;
8177
}
82-
83-
if (first)
84-
{
85-
first = false;
86-
if (decoded[namePos] == '?')
87-
namePos++;
8878
}
8979

9080
string name;
@@ -95,23 +85,20 @@ private static void ParseQueryString(string query, Encoding encoding, NameValueC
9585
}
9686
else
9787
{
98-
name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding);
88+
name = UrlDecode(query.Substring(namePos, valuePos - namePos - 1), encoding);
9989
}
90+
10091
if (valueEnd < 0)
10192
{
102-
namePos = -1;
103-
valueEnd = decoded.Length;
93+
valueEnd = query.Length;
10494
}
105-
else
106-
{
107-
namePos = valueEnd + 1;
108-
}
109-
var value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding);
11095

96+
namePos = valueEnd + 1;
97+
string value = UrlDecode(query.Substring(valuePos, valueEnd - valuePos), encoding);
11198
result.Add(name, value);
112-
if (namePos == -1)
113-
break;
11499
}
100+
101+
return result;
115102
}
116103

117104
public static string HtmlDecode(string s)
@@ -298,4 +285,4 @@ public static string JavaScriptStringEncode(string value, bool addDoubleQuotes)
298285
return addDoubleQuotes ? "\"" + encoded + "\"" : encoded;
299286
}
300287
}
301-
}
288+
}

src/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs

Lines changed: 156 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
using System.IO;
3434
using System.Linq;
3535
using System.Text;
36-
using System.Web;
3736
using Xunit;
3837

3938
namespace System.Web.Tests
@@ -51,6 +50,8 @@ public class HttpUtilityTest
5150
new object[] {"&lt;script>", "<script>"},
5251
new object[] {"&quot;a&amp;b&quot;", "\"a&b\""},
5352
new object[] {"&#39;string&#39;", "'string'"},
53+
new object[] {"abc + def!", "abc + def!"},
54+
new object[] {"This is an &lt;element>!", "This is an <element>!"},
5455
};
5556

5657
[Theory]
@@ -240,11 +241,9 @@ public void HtmlDecode_TextWriter(string decoded, string encoded)
240241
{
241242
"\u00A0¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ",
242243
@"&#160;&#161;&#162;&#163;&#164;&#165;&#166;&#167;&#168;&#169;&#170;&#171;&#172;&#173;&#174;&#175;&#176;&#177;&#178;&#179;&#180;&#181;&#182;&#183;&#184;&#185;&#186;&#187;&#188;&#189;&#190;&#191;&#192;&#193;&#194;&#195;&#196;&#197;&#198;&#199;&#200;&#201;&#202;&#203;&#204;&#205;&#206;&#207;&#208;&#209;&#210;&#211;&#212;&#213;&#214;&#215;&#216;&#217;&#218;&#219;&#220;&#221;&#222;&#223;&#224;&#225;&#226;&#227;&#228;&#229;&#230;&#231;&#232;&#233;&#234;&#235;&#236;&#237;&#238;&#239;&#240;&#241;&#242;&#243;&#244;&#245;&#246;&#247;&#248;&#249;&#250;&#251;&#252;&#253;&#254;&#255;",
243-
},
244+
}
244245
};
245246

246-
247-
248247
[Theory]
249248
[MemberData(nameof(HtmlEncodeDecodeData))]
250249
[MemberData(nameof(HtmlEncodeData))]
@@ -254,6 +253,16 @@ public void HtmlEncode(string decoded, string encoded)
254253
Assert.Equal(encoded, HttpUtility.HtmlEncode(decoded));
255254
}
256255

256+
[Theory]
257+
[MemberData(nameof(HtmlEncodeDecodeData))]
258+
[MemberData(nameof(HtmlEncodeData))]
259+
[InlineData(null, null)]
260+
[InlineData(2, "2")]
261+
public void HtmlEncodeObject(string decoded, string encoded)
262+
{
263+
Assert.Equal(encoded, HttpUtility.HtmlEncode((object)decoded));
264+
}
265+
257266
[Theory]
258267
[MemberData(nameof(HtmlEncodeDecodeData))]
259268
[MemberData(nameof(HtmlEncodeData))]
@@ -284,6 +293,8 @@ public static IEnumerable<object[]> JavaScriptStringEncodeData
284293
{
285294
yield return new object[] { null, "" };
286295
yield return new object[] { "", "" };
296+
yield return new object[] {"No escaping needed.", "No escaping needed."};
297+
yield return new object[] {"The \t and \n will need to be escaped.", "The \\t and \\n will need to be escaped."};
287298
for (char c = char.MinValue; c < TestMaxChar; c++)
288299
{
289300
if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 38 || c == 39 || c == 60 || c == 62 || c == 133 || c == 8232 || c == 8233)
@@ -365,10 +376,18 @@ private static string UnicodeStr
365376
new[] {new[] {UnicodeStr}}
366377
},
367378
new object[] {"name=value=test", new[] {"name"}, new[] {new[] {"value=test"}}},
379+
new object[] { "name=value&#xe9;", new[] {"name", null}, new[] {new[] {"value"}, new[] { "#xe9;" } }},
380+
new object[] { "name=value&amp;name2=value2", new[] {"name", "amp;name2"}, new[] {new[] {"value"}, new[] { "value2" } }},
381+
new object[] {"name=value=test+phrase", new[] {"name"}, new[] {new[] {"value=test phrase"}}},
368382
};
369383

370384
public static IEnumerable<object[]> ParseQueryStringDataQ =>
371-
ParseQueryStringData.Select(a => new object[] { "?" + (string)a[0] }.Concat(a.Skip(1)).ToArray());
385+
ParseQueryStringData.Select(a => new object[] { "?" + (string)a[0] }.Concat(a.Skip(1)).ToArray())
386+
.Concat(new[]
387+
{
388+
new object[] { "??name=value=test", new[] { "?name" }, new[] { new[] { "value=test" }}},
389+
new object[] { "?", Array.Empty<string>(), Array.Empty<IList<string>>()}
390+
});
372391

373392
[Theory]
374393
[MemberData(nameof(ParseQueryStringData))]
@@ -407,10 +426,12 @@ public void ParseQueryString_Encoding_null()
407426
public void ParseQueryString_ToString()
408427
{
409428
var parsed = HttpUtility.ParseQueryString("");
429+
Assert.Empty(parsed.ToString());
410430
parsed.Add("ReturnUrl", @"http://localhost/login/authenticate?ReturnUrl=http://localhost/secured_area&__provider__=google");
411431

412432
var expected = "ReturnUrl=http%3a%2f%2flocalhost%2flogin%2fauthenticate%3fReturnUrl%3dhttp%3a%2f%2flocalhost%2fsecured_area%26__provider__%3dgoogle";
413433
Assert.Equal(expected, parsed.ToString());
434+
Assert.Equal(expected, HttpUtility.ParseQueryString(expected).ToString());
414435
}
415436

416437
#endregion ParseQueryString
@@ -428,6 +449,12 @@ public void ParseQueryString_ToString()
428449
new object[] {"http://127.0.0.1:8080/appDir/page.aspx?foo=bar", "http://127.0.0.1:8080/appDir/page.aspx?foo=b%u0061r"},
429450
new object[] {"http://127.0.0.1:8080/appDir/page.aspx?foo=b%ar", "http://127.0.0.1:8080/appDir/page.aspx?foo=b%%u0061r"},
430451
new object[] {"http://127.0.0.1:8080/appDir/page.aspx?foo=b%uu0061r", "http://127.0.0.1:8080/appDir/page.aspx?foo=b%uu0061r"},
452+
new object[] {"http://127.0.0.1:8080/appDir/page.aspx?foo=bar baz", "http://127.0.0.1:8080/appDir/page.aspx?foo=bar+baz"},
453+
new object[] { "http://example.net/\U00010000", "http://example.net/\U00010000" },
454+
new object[] { "http://example.net/\uFFFD", "http://example.net/\uD800" },
455+
new object[] { "http://example.net/\uFFFDa", "http://example.net/\uD800a" },
456+
new object[] { "http://example.net/\uFFFD", "http://example.net/\uDC00" },
457+
new object[] { "http://example.net/\uFFFDa", "http://example.net/\uDC00a" }
431458
};
432459

433460
public static IEnumerable<object[]> UrlDecodeDataToBytes =>
@@ -441,6 +468,12 @@ public void ParseQueryString_ToString()
441468
new object[] {"http://127.0.0.1:8080/appDir/page.aspx?foo=b%uu0061r", "http://127.0.0.1:8080/appDir/page.aspx?foo=b%uu0061r"},
442469
new object[] {"http://127.0.0.1:8080/appDir/page.aspx?foo=b%u0061r", "http://127.0.0.1:8080/appDir/page.aspx?foo=b%u0061r"},
443470
new object[] {"http://127.0.0.1:8080/appDir/page.aspx?foo=b%%u0061r", "http://127.0.0.1:8080/appDir/page.aspx?foo=b%%u0061r"},
471+
new object[] {"http://127.0.0.1:8080/appDir/page.aspx?foo=bar baz", "http://127.0.0.1:8080/appDir/page.aspx?foo=bar+baz"},
472+
new object[] { "http://example.net/\U00010000", "http://example.net/\U00010000" },
473+
new object[] { "http://example.net/\uFFFD", "http://example.net/\uD800" },
474+
new object[] { "http://example.net/\uFFFDa", "http://example.net/\uD800a" },
475+
new object[] { "http://example.net/\uFFFD", "http://example.net/\uDC00" },
476+
new object[] { "http://example.net/\uFFFDa", "http://example.net/\uDC00a" }
444477
};
445478

446479
[Theory]
@@ -450,13 +483,60 @@ public void UrlDecode(string decoded, string encoded)
450483
Assert.Equal(decoded, HttpUtility.UrlDecode(encoded));
451484
}
452485

486+
[Fact]
487+
public void UrlDecode_null()
488+
{
489+
Assert.Null(HttpUtility.UrlDecode(default(string), Encoding.UTF8));
490+
Assert.Null(HttpUtility.UrlDecode(default(byte[]), Encoding.UTF8));
491+
Assert.Null(HttpUtility.UrlDecode(null));
492+
Assert.Null(HttpUtility.UrlDecode(null, 2, 0, Encoding.UTF8));
493+
Assert.Throws<ArgumentNullException>("bytes", () => HttpUtility.UrlDecode(null, 2, 3, Encoding.UTF8));
494+
}
495+
496+
[Fact]
497+
public void UrlDecode_OutOfRange()
498+
{
499+
byte[] bytes = { 0, 1, 2 };
500+
Assert.Throws<ArgumentOutOfRangeException>("offset", () => HttpUtility.UrlDecode(bytes, -1, 2, Encoding.UTF8));
501+
Assert.Throws<ArgumentOutOfRangeException>("offset", () => HttpUtility.UrlDecode(bytes, 14, 2, Encoding.UTF8));
502+
Assert.Throws<ArgumentOutOfRangeException>("count", () => HttpUtility.UrlDecode(bytes, 1, 12, Encoding.UTF8));
503+
Assert.Throws<ArgumentOutOfRangeException>("count", () => HttpUtility.UrlDecode(bytes, 1, -12, Encoding.UTF8));
504+
}
505+
453506
[Theory]
454507
[MemberData(nameof(UrlDecodeDataToBytes))]
455508
public void UrlDecodeToBytes(string decoded, string encoded)
456509
{
457510
Assert.Equal(decoded, Encoding.UTF8.GetString(HttpUtility.UrlDecodeToBytes(encoded, Encoding.UTF8)));
458511
}
459512

513+
[Theory]
514+
[MemberData(nameof(UrlDecodeDataToBytes))]
515+
public void UrlDecodeToBytes_DefaultEncoding(string decoded, string encoded)
516+
{
517+
Assert.Equal(decoded, Encoding.UTF8.GetString(HttpUtility.UrlDecodeToBytes(encoded)));
518+
}
519+
520+
[Fact]
521+
public void UrlDecodeToBytes_null()
522+
{
523+
Assert.Null(HttpUtility.UrlDecodeToBytes(default(byte[])));
524+
Assert.Null(HttpUtility.UrlDecodeToBytes(default(string)));
525+
Assert.Null(HttpUtility.UrlDecodeToBytes(default(string), Encoding.UTF8));
526+
Assert.Null(HttpUtility.UrlDecodeToBytes(default(byte[]), 2, 0));
527+
Assert.Throws<ArgumentNullException>("bytes", () => HttpUtility.UrlDecodeToBytes(default(byte[]), 2, 3));
528+
}
529+
530+
[Fact]
531+
public void UrlDecodeToBytes_OutOfRange()
532+
{
533+
byte[] bytes = { 0, 1, 2 };
534+
Assert.Throws<ArgumentOutOfRangeException>("offset", () => HttpUtility.UrlDecodeToBytes(bytes, -1, 2));
535+
Assert.Throws<ArgumentOutOfRangeException>("offset", () => HttpUtility.UrlDecodeToBytes(bytes, 14, 2));
536+
Assert.Throws<ArgumentOutOfRangeException>("count", () => HttpUtility.UrlDecodeToBytes(bytes, 1, 12));
537+
Assert.Throws<ArgumentOutOfRangeException>("count", () => HttpUtility.UrlDecodeToBytes(bytes, 1, -12));
538+
}
539+
460540
[Theory]
461541
[MemberData(nameof(UrlDecodeData))]
462542
public void UrlDecode_ByteArray(string decoded, string encoded)
@@ -529,10 +609,57 @@ public void UrlEncodeToBytes(string decoded, string encoded)
529609
Assert.Equal(encoded, Encoding.UTF8.GetString(HttpUtility.UrlEncodeToBytes(decoded, Encoding.UTF8)));
530610
}
531611

612+
[Theory]
613+
[MemberData(nameof(UrlEncodeData))]
614+
public void UrlEncodeToBytes_DefaultEncoding(string decoded, string encoded)
615+
{
616+
Assert.Equal(encoded, Encoding.UTF8.GetString(HttpUtility.UrlEncodeToBytes(decoded)));
617+
}
618+
619+
[Theory, MemberData(nameof(UrlEncodeData))]
620+
public void UrlEncodeToBytesExplicitSize(string decoded, string encoded)
621+
{
622+
byte[] bytes = Encoding.UTF8.GetBytes(decoded);
623+
Assert.Equal(encoded, Encoding.UTF8.GetString(HttpUtility.UrlEncodeToBytes(bytes, 0, bytes.Length)));
624+
}
625+
626+
627+
[Theory]
628+
[InlineData(" abc defgh", "abc+def", 1, 7)]
629+
[InlineData(" abc defgh", "", 1, 0)]
630+
public void UrlEncodeToBytesExplicitSize(string decoded, string encoded, int offset, int count)
631+
{
632+
byte[] bytes = Encoding.UTF8.GetBytes(decoded);
633+
Assert.Equal(encoded, Encoding.UTF8.GetString(HttpUtility.UrlEncodeToBytes(bytes, offset, count)));
634+
}
635+
636+
[Theory]
637+
[InlineData("abc def", " abc+defgh", 1, 7)]
638+
[InlineData("", " abc defgh", 1, 0)]
639+
public void UrlDecodeToBytesExplicitSize(string decoded, string encoded, int offset, int count)
640+
{
641+
byte[] bytes = Encoding.UTF8.GetBytes(encoded);
642+
Assert.Equal(decoded, Encoding.UTF8.GetString(HttpUtility.UrlDecodeToBytes(bytes, offset, count)));
643+
}
644+
532645
[Fact]
533646
public void UrlEncodeToBytes_null()
534647
{
535648
Assert.Null(HttpUtility.UrlEncodeToBytes(null, Encoding.UTF8));
649+
Assert.Null(HttpUtility.UrlEncodeToBytes(default(byte[])));
650+
Assert.Null(HttpUtility.UrlEncodeToBytes(default(string)));
651+
Assert.Null(HttpUtility.UrlEncodeToBytes(null, 2, 0));
652+
Assert.Throws<ArgumentNullException>("bytes", () => HttpUtility.UrlEncodeToBytes(null, 2, 3));
653+
}
654+
655+
[Fact]
656+
public void UrlEncodeToBytes_OutOfRange()
657+
{
658+
byte[] bytes = { 0, 1, 2 };
659+
Assert.Throws<ArgumentOutOfRangeException>("offset", () => HttpUtility.UrlEncodeToBytes(bytes, -1, 2));
660+
Assert.Throws<ArgumentOutOfRangeException>("offset", () => HttpUtility.UrlEncodeToBytes(bytes, 14, 2));
661+
Assert.Throws<ArgumentOutOfRangeException>("count", () => HttpUtility.UrlEncodeToBytes(bytes, 1, 12));
662+
Assert.Throws<ArgumentOutOfRangeException>("count", () => HttpUtility.UrlEncodeToBytes(bytes, 1, -12));
536663
}
537664

538665
[Theory]
@@ -543,9 +670,22 @@ public void UrlEncode_ByteArray(string decoded, string encoded)
543670
}
544671

545672
[Fact]
546-
public void UrlEncode_ByteArray_null()
673+
public void UrlEncode_null()
674+
{
675+
Assert.Null(HttpUtility.UrlEncode((byte[])null));
676+
Assert.Null(HttpUtility.UrlEncode((string)null));
677+
Assert.Null(HttpUtility.UrlEncode(null, Encoding.UTF8));
678+
Assert.Null(HttpUtility.UrlEncode(null, 2, 3));
679+
}
680+
681+
[Fact]
682+
public void UrlEncode_OutOfRange()
547683
{
548-
Assert.Null(HttpUtility.UrlEncode((byte[]) null));
684+
byte[] bytes = {0, 1, 2};
685+
Assert.Throws<ArgumentOutOfRangeException>("offset", () => HttpUtility.UrlEncode(bytes, -1, 2));
686+
Assert.Throws<ArgumentOutOfRangeException>("offset", () => HttpUtility.UrlEncode(bytes, 14, 2));
687+
Assert.Throws<ArgumentOutOfRangeException>("count", () => HttpUtility.UrlEncode(bytes, 1, 12));
688+
Assert.Throws<ArgumentOutOfRangeException>("count", () => HttpUtility.UrlEncode(bytes, 1, -12));
549689
}
550690

551691
#endregion UrlEncode(ToBytes)
@@ -591,6 +731,15 @@ public void UrlEncodeUnicodeToBytes(string decoded, string encoded)
591731
[InlineData(" ", "%20")]
592732
[InlineData("\n", "%0a")]
593733
[InlineData("default.xxx?sdsd=sds", "default.xxx?sdsd=sds")]
734+
[InlineData("?sdsd=sds", "?sdsd=sds")]
735+
[InlineData("", "")]
736+
[InlineData("http://example.net/default.xxx?sdsd=sds", "http://example.net/default.xxx?sdsd=sds")]
737+
[InlineData("http://example.net:8080/default.xxx?sdsd=sds", "http://example.net:8080/default.xxx?sdsd=sds")]
738+
[InlineData("http://eXample.net:80/default.xxx?sdsd=sds", "http://eXample.net:80/default.xxx?sdsd=sds")]
739+
[InlineData("http://EXAMPLE.NET/default.xxx?sdsd=sds", "http://EXAMPLE.NET/default.xxx?sdsd=sds")]
740+
[InlineData("http://EXAMPLE.NET/défault.xxx?sdsd=sds", "http://EXAMPLE.NET/d%c3%a9fault.xxx?sdsd=sds")]
741+
[InlineData("file:///C/Users", "file:///C/Users")]
742+
[InlineData("mailto:user@example.net", "mailto:user@example.net")]
594743
public void UrlPathEncode(string decoded, string encoded)
595744
{
596745
Assert.Equal(encoded, HttpUtility.UrlPathEncode(decoded));

0 commit comments

Comments
 (0)