Skip to content
This repository was archived by the owner on Mar 19, 2019. It is now read-only.

Commit 88b1e36

Browse files
author
Cesar Blum Silveira
committed
Do not unescape forward slashes in the request path (#146).
1 parent 905b5bc commit 88b1e36

File tree

4 files changed

+26
-32
lines changed

4 files changed

+26
-32
lines changed

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"projects": ["src"]
2+
"projects": ["src", "test"]
33
}

src/Microsoft.Net.Http.Server/RequestProcessing/RequestUriBuilder.cs

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ internal sealed class RequestUriBuilder
5050
private StringBuilder _requestUriString;
5151

5252
// The raw path is parsed by looping through all characters from left to right. 'rawOctets'
53-
// is used to store consecutive percent encoded octets as actual byte values: e.g. for path /pa%C3%84th%2F/
54-
// rawOctets will be set to { 0xC3, 0x84 } when we reach character 't' and it will be { 0x2F } when
53+
// is used to store consecutive percent encoded octets as actual byte values: e.g. for path /pa%C3%84th%20/
54+
// rawOctets will be set to { 0xC3, 0x84 } when we reach character 't' and it will be { 0x20 } when
5555
// we reach the final '/'. I.e. after a sequence of percent encoded octets ends, we use rawOctets as
5656
// input to the encoding and percent encode the resulting string into UTF-8 octets.
5757
//
@@ -68,7 +68,7 @@ internal sealed class RequestUriBuilder
6868
static RequestUriBuilder()
6969
{
7070
// TODO: False triggers more detailed/correct parsing, but it's rather slow.
71-
UseCookedRequestUrl = true; // SettingsSectionInternal.Section.HttpListenerUnescapeRequestUrl;
71+
UseCookedRequestUrl = false; // SettingsSectionInternal.Section.HttpListenerUnescapeRequestUrl;
7272
Utf8Encoding = new UTF8Encoding(false, true);
7373
#if DOTNET5_4
7474
AnsiEncoding = Utf8Encoding;
@@ -323,7 +323,7 @@ private ParsingResult ParseRawPath(Encoding encoding)
323323
if (current == '%')
324324
{
325325
// Assert is enough, since http.sys accepted the request string already. This should never happen.
326-
Debug.Assert(index + 2 < _rawPath.Length, "Expected >=2 characters after '%' (e.g. %2F)");
326+
Debug.Assert(index + 2 < _rawPath.Length, "Expected >=2 characters after '%' (e.g. %20)");
327327

328328
index++;
329329
current = _rawPath[index];
@@ -332,9 +332,7 @@ private ParsingResult ParseRawPath(Encoding encoding)
332332
// We found "%u" which means, we have a Unicode code point of the form "%uXXXX".
333333
Debug.Assert(index + 4 < _rawPath.Length, "Expected >=4 characters after '%u' (e.g. %u0062)");
334334

335-
// Decode the content of rawOctets into percent encoded UTF-8 characters and append them
336-
// to requestUriString.
337-
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
335+
if (!EmptyDecodeAndAppendDecodedOctetsList(encoding))
338336
{
339337
return ParsingResult.EncodingError;
340338
}
@@ -346,19 +344,26 @@ private ParsingResult ParseRawPath(Encoding encoding)
346344
}
347345
else
348346
{
349-
// We found '%', but not followed by 'u', i.e. we have a percent encoded octed: %XX
350-
if (!AddPercentEncodedOctetToRawOctetsList(encoding, _rawPath.Substring(index, 2)))
347+
// We found '%', but not followed by 'u', i.e. we have a percent encoded octet: %XX
348+
var octetString = _rawPath.Substring(index, 2);
349+
350+
// Leave %2F as is, otherwise add to raw octets list for unescaping
351+
if (octetString == "2F" || octetString == "2f")
352+
{
353+
_requestUriString.Append('%');
354+
_requestUriString.Append(octetString);
355+
}
356+
else if (!AddPercentEncodedOctetToRawOctetsList(encoding, octetString))
351357
{
352358
return ParsingResult.InvalidString;
353359
}
360+
354361
index += 2;
355362
}
356363
}
357364
else
358365
{
359-
// We found a non-'%' character: decode the content of rawOctets into percent encoded
360-
// UTF-8 characters and append it to the result.
361-
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
366+
if (!EmptyDecodeAndAppendDecodedOctetsList(encoding))
362367
{
363368
return ParsingResult.EncodingError;
364369
}
@@ -370,7 +375,7 @@ private ParsingResult ParseRawPath(Encoding encoding)
370375

371376
// if the raw path ends with a sequence of percent encoded octets, make sure those get added to the
372377
// result (requestUriString).
373-
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
378+
if (!EmptyDecodeAndAppendDecodedOctetsList(encoding))
374379
{
375380
return ParsingResult.EncodingError;
376381
}
@@ -424,7 +429,7 @@ private bool AddPercentEncodedOctetToRawOctetsList(Encoding encoding, string esc
424429
return true;
425430
}
426431

427-
private bool EmptyDecodeAndAppendRawOctetsList(Encoding encoding)
432+
private bool EmptyDecodeAndAppendDecodedOctetsList(Encoding encoding)
428433
{
429434
if (_rawOctets.Count == 0)
430435
{
@@ -436,30 +441,17 @@ private bool EmptyDecodeAndAppendRawOctetsList(Encoding encoding)
436441
{
437442
// If the encoding can get a string out of the byte array, this is a valid string in the
438443
// 'encoding' encoding.
439-
byte[] bytes = _rawOctets.ToArray();
444+
var bytes = _rawOctets.ToArray();
440445
decodedString = encoding.GetString(bytes, 0, bytes.Length);
441446

442-
if (encoding == Utf8Encoding)
443-
{
444-
AppendOctetsPercentEncoded(_requestUriString, bytes);
445-
}
446-
else
447-
{
448-
AppendOctetsPercentEncoded(_requestUriString, Utf8Encoding.GetBytes(decodedString));
449-
}
450-
447+
_requestUriString.Append(decodedString);
451448
_rawOctets.Clear();
452449

453450
return true;
454451
}
455452
catch (DecoderFallbackException e)
456453
{
457-
LogWarning("EmptyDecodeAndAppendRawOctetsList", "Can't convert bytes: " + GetOctetsAsString(_rawOctets), e.Message);
458-
}
459-
catch (EncoderFallbackException e)
460-
{
461-
// If utf8Encoding.GetBytes() fails
462-
LogWarning("EmptyDecodeAndAppendRawOctetsList", "Can't convert bytes: " + decodedString, e.Message);
454+
LogWarning(nameof(EmptyDecodeAndAppendDecodedOctetsList), "Can't convert bytes: " + GetOctetsAsString(_rawOctets), e.Message);
463455
}
464456

465457
return false;

test/Microsoft.AspNet.Server.WebListener.FunctionalTests/RequestTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public async Task Request_SimpleGet_Success()
8787
[InlineData("/basepath/", "/basepath/subpath", "/basepath", "/subpath")]
8888
[InlineData("/base path/", "/base%20path/sub path", "/base path", "/sub path")]
8989
[InlineData("/base葉path/", "/base%E8%91%89path/sub%E8%91%89path", "/base葉path", "/sub葉path")]
90+
[InlineData("/basepath/", "/basepath/sub%2Fpath", "/basepath", "/sub%2Fpath")]
9091
public async Task Request_PathSplitting(string pathBase, string requestPath, string expectedPathBase, string expectedPath)
9192
{
9293
string root;

test/Microsoft.Net.Http.Server.FunctionalTests/RequestTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public async Task Request_SimpleGet_Success()
5656
[InlineData("/basepath/", "/basepath/subpath", "/basepath", "/subpath")]
5757
[InlineData("/base path/", "/base%20path/sub path", "/base path", "/sub path")]
5858
[InlineData("/base葉path/", "/base%E8%91%89path/sub%E8%91%89path", "/base葉path", "/sub葉path")]
59+
[InlineData("/basepath/", "/basepath/sub%2Fpath", "/basepath", "/sub%2Fpath")]
5960
public async Task Request_PathSplitting(string pathBase, string requestPath, string expectedPathBase, string expectedPath)
6061
{
6162
string root;
@@ -70,8 +71,8 @@ public async Task Request_PathSplitting(string pathBase, string requestPath, str
7071

7172
// Request Keys
7273
Assert.Equal("http", request.Scheme);
73-
Assert.Equal(expectedPath, request.Path);
7474
Assert.Equal(expectedPathBase, request.PathBase);
75+
Assert.Equal(expectedPath, request.Path);
7576
Assert.Equal(string.Empty, request.QueryString);
7677
context.Dispose();
7778

0 commit comments

Comments
 (0)