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

Commit e56b0bc

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

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
@@ -84,6 +84,7 @@ public Frame(ConnectionContext context)
8484
public string PathBase { get; set; }
8585
public string Path { get; set; }
8686
public string QueryString { get; set; }
87+
public string RawTarget { get; set; }
8788
public string HttpVersion
8889
{
8990
get
@@ -849,6 +850,8 @@ protected RequestLineStatus TakeStartLine(SocketInput input)
849850
queryString = begin.GetAsciiString(scan);
850851
}
851852

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

912930
requestUrlPath = PathNormalizer.RemoveDotSegments(requestUrlPath);
913931

914932
consumed = scan;
915933
Method = method;
916934
QueryString = queryString;
935+
RawTarget = rawTarget;
917936
HttpVersion = httpVersion;
918937

919938
bool caseMatches;
920-
921-
if (!string.IsNullOrEmpty(_pathBase) &&
922-
(requestUrlPath.Length == _pathBase.Length || (requestUrlPath.Length > _pathBase.Length && requestUrlPath[_pathBase.Length] == '/')) &&
923-
RequestUrlStartsWithPathBase(requestUrlPath, out caseMatches))
939+
if (RequestUrlStartsWithPathBase(requestUrlPath, out caseMatches))
924940
{
925941
PathBase = caseMatches ? _pathBase : requestUrlPath.Substring(0, _pathBase.Length);
926942
Path = requestUrlPath.Substring(_pathBase.Length);
927943
}
928-
else
944+
else if (rawTarget[0] == '/') // check rawTarget since requestUrlPath can be "" or "/" after dot segment removal
929945
{
930946
Path = requestUrlPath;
931947
}
948+
else
949+
{
950+
Path = string.Empty;
951+
PathBase = string.Empty;
952+
QueryString = string.Empty;
953+
}
932954

933955
return RequestLineStatus.Done;
934956
}
@@ -942,6 +964,16 @@ private bool RequestUrlStartsWithPathBase(string requestUrl, out bool caseMatche
942964
{
943965
caseMatches = true;
944966

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