Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit efa1890

Browse files
author
Cesar Blum Silveira
committed
Implement IHttpRequestFeature.RawTarget (aspnet/HttpAbstractions#596).
1 parent 6d3a416 commit efa1890

File tree

3 files changed

+166
-5
lines changed

3 files changed

+166
-5
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.FeatureCollection.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ string IHttpRequestFeature.QueryString
152152
}
153153
}
154154

155+
string IHttpRequestFeature.RawTarget
156+
{
157+
get
158+
{
159+
return RawTarget;
160+
}
161+
set
162+
{
163+
RawTarget = value;
164+
}
165+
}
166+
155167
IHeaderDictionary IHttpRequestFeature.Headers
156168
{
157169
get

src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public Frame(ConnectionContext context)
8585
public string PathBase { get; set; }
8686
public string Path { get; set; }
8787
public string QueryString { get; set; }
88+
public string RawTarget { get; set; }
8889
public string HttpVersion
8990
{
9091
get
@@ -848,6 +849,8 @@ protected RequestLineStatus TakeStartLine(SocketInput input)
848849
queryString = begin.GetAsciiString(scan);
849850
}
850851

852+
var queryEnd = scan;
853+
851854
if (pathBegin.Peek() == ' ')
852855
{
853856
RejectRequest("Missing request target.");
@@ -895,8 +898,12 @@ protected RequestLineStatus TakeStartLine(SocketInput input)
895898
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
896899
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
897900
string requestUrlPath;
901+
string rawTarget;
898902
if (needDecode)
899903
{
904+
// Read raw target before mutating memory.
905+
rawTarget = pathBegin.GetAsciiString(queryEnd);
906+
900907
// URI was encoded, unescape and then parse as utf8
901908
pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd);
902909
requestUrlPath = pathBegin.GetUtf8String(pathEnd);
@@ -906,28 +913,43 @@ protected RequestLineStatus TakeStartLine(SocketInput input)
906913
{
907914
// URI wasn't encoded, parse as ASCII
908915
requestUrlPath = pathBegin.GetAsciiString(pathEnd);
916+
917+
if (queryString.Length == 0)
918+
{
919+
// No need to allocate an extra string if the path didn't need
920+
// decoding and there's no query string following it.
921+
rawTarget = requestUrlPath;
922+
}
923+
else
924+
{
925+
rawTarget = pathBegin.GetAsciiString(queryEnd);
926+
}
909927
}
910928

911929
requestUrlPath = PathNormalizer.RemoveDotSegments(requestUrlPath);
912930

913931
consumed = scan;
914932
Method = method;
915933
QueryString = queryString;
934+
RawTarget = rawTarget;
916935
HttpVersion = httpVersion;
917936

918937
bool caseMatches;
919-
920-
if (!string.IsNullOrEmpty(_pathBase) &&
921-
(requestUrlPath.Length == _pathBase.Length || (requestUrlPath.Length > _pathBase.Length && requestUrlPath[_pathBase.Length] == '/')) &&
922-
RequestUrlStartsWithPathBase(requestUrlPath, out caseMatches))
938+
if (RequestUrlStartsWithPathBase(requestUrlPath, out caseMatches))
923939
{
924940
PathBase = caseMatches ? _pathBase : requestUrlPath.Substring(0, _pathBase.Length);
925941
Path = requestUrlPath.Substring(_pathBase.Length);
926942
}
927-
else
943+
else if (rawTarget[0] == '/') // check rawTarget since requestUrlPath can be "" or "/" after dot segment removal
928944
{
929945
Path = requestUrlPath;
930946
}
947+
else
948+
{
949+
Path = string.Empty;
950+
PathBase = string.Empty;
951+
QueryString = string.Empty;
952+
}
931953

932954
return RequestLineStatus.Done;
933955
}
@@ -941,6 +963,16 @@ private bool RequestUrlStartsWithPathBase(string requestUrl, out bool caseMatche
941963
{
942964
caseMatches = true;
943965

966+
if (string.IsNullOrEmpty(_pathBase))
967+
{
968+
return false;
969+
}
970+
971+
if (requestUrl.Length < _pathBase.Length || (requestUrl.Length > _pathBase.Length && requestUrl[_pathBase.Length] != '/'))
972+
{
973+
return false;
974+
}
975+
944976
for (var i = 0; i < _pathBase.Length; i++)
945977
{
946978
if (requestUrl[i] != _pathBase[i])

test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.AspNetCore.Builder;
1111
using Microsoft.AspNetCore.Hosting;
1212
using Microsoft.AspNetCore.Http;
13+
using Microsoft.AspNetCore.Http.Features;
1314
using Microsoft.AspNetCore.Testing.xunit;
1415
using Newtonsoft.Json;
1516
using Newtonsoft.Json.Linq;
@@ -202,6 +203,122 @@ public void RequestPathIsNormalized()
202203
}
203204
}
204205

206+
[Theory]
207+
[InlineData("/")]
208+
[InlineData("/.")]
209+
[InlineData("/..")]
210+
[InlineData("/./.")]
211+
[InlineData("/./..")]
212+
[InlineData("/../.")]
213+
[InlineData("/../..")]
214+
[InlineData("/path")]
215+
[InlineData("/path?foo=1&bar=2")]
216+
[InlineData("/hello%20world")]
217+
[InlineData("/hello%20world?foo=1&bar=2")]
218+
[InlineData("/base/path")]
219+
[InlineData("/base/path?foo=1&bar=2")]
220+
[InlineData("/base/hello%20world")]
221+
[InlineData("/base/hello%20world?foo=1&bar=2")]
222+
public void RequestFeatureContainsRawTarget(string requestTarget)
223+
{
224+
var builder = new WebHostBuilder()
225+
.UseKestrel()
226+
.UseUrls($"http://127.0.0.1:0/base")
227+
.Configure(app =>
228+
{
229+
app.Run(async context =>
230+
{
231+
var connection = context.Connection;
232+
Assert.Equal(requestTarget, context.Features.Get<IHttpRequestFeature>().RawTarget);
233+
await context.Response.WriteAsync("hello, world");
234+
});
235+
});
236+
237+
using (var host = builder.Build())
238+
{
239+
host.Start();
240+
241+
using (var socket = TestConnection.CreateConnectedLoopbackSocket(host.GetPort()))
242+
{
243+
socket.Send(Encoding.ASCII.GetBytes($"GET {requestTarget} HTTP/1.1\r\n\r\n"));
244+
socket.Shutdown(SocketShutdown.Send);
245+
246+
var response = new StringBuilder();
247+
var buffer = new byte[4096];
248+
while (true)
249+
{
250+
var length = socket.Receive(buffer);
251+
if (length == 0)
252+
{
253+
break;
254+
}
255+
256+
response.Append(Encoding.ASCII.GetString(buffer, 0, length));
257+
}
258+
259+
Assert.StartsWith("HTTP/1.1 200 OK", response.ToString());
260+
}
261+
}
262+
}
263+
264+
[Theory]
265+
[InlineData("*")]
266+
[InlineData("*/?arg=value")]
267+
[InlineData("*?arg=value")]
268+
[InlineData("DoesNotStartWith/")]
269+
[InlineData("DoesNotStartWith/?arg=value")]
270+
[InlineData("DoesNotStartWithSlash?arg=value")]
271+
[InlineData("./")]
272+
[InlineData("../")]
273+
[InlineData("../.")]
274+
[InlineData(".././")]
275+
[InlineData("../..")]
276+
[InlineData("../../")]
277+
public void NonPathRequestTargetSetInRawTarget(string requestTarget)
278+
{
279+
var builder = new WebHostBuilder()
280+
.UseKestrel()
281+
.UseUrls($"http://127.0.0.1:0/")
282+
.Configure(app =>
283+
{
284+
app.Run(async context =>
285+
{
286+
var connection = context.Connection;
287+
Assert.Equal(requestTarget, context.Features.Get<IHttpRequestFeature>().RawTarget);
288+
Assert.Empty(context.Request.Path.Value);
289+
Assert.Empty(context.Request.PathBase.Value);
290+
Assert.Empty(context.Request.QueryString.Value);
291+
await context.Response.WriteAsync("hello, world");
292+
});
293+
});
294+
295+
using (var host = builder.Build())
296+
{
297+
host.Start();
298+
299+
using (var socket = TestConnection.CreateConnectedLoopbackSocket(host.GetPort()))
300+
{
301+
socket.Send(Encoding.ASCII.GetBytes($"GET {requestTarget} HTTP/1.1\r\n\r\n"));
302+
socket.Shutdown(SocketShutdown.Send);
303+
304+
var response = new StringBuilder();
305+
var buffer = new byte[4096];
306+
while (true)
307+
{
308+
var length = socket.Receive(buffer);
309+
if (length == 0)
310+
{
311+
break;
312+
}
313+
314+
response.Append(Encoding.ASCII.GetString(buffer, 0, length));
315+
}
316+
317+
Assert.StartsWith("HTTP/1.1 200 OK", response.ToString());
318+
}
319+
}
320+
}
321+
205322
private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress)
206323
{
207324
var builder = new WebHostBuilder()

0 commit comments

Comments
 (0)