Skip to content

Commit 3fb62af

Browse files
authored
Add overloads of HttpContext.RewritePath (#327)
We added HttpContext.RewritePath with a single path parameter. This adds the rest of the overloads to support optionally setting the FilePath/PathInfo, support for which was added a while back. Fixes #315
1 parent 9412e6b commit 3fb62af

File tree

12 files changed

+301
-97
lines changed

12 files changed

+301
-97
lines changed

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/HttpRequestInputStreamFeature.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414

1515
namespace Microsoft.AspNetCore.SystemWebAdapters;
1616

17-
internal class HttpRequestInputStreamFeature : IHttpRequestInputStreamFeature, IHttpRequestFeature, IRequestBodyPipeFeature, IDisposable
17+
internal class HttpRequestInputStreamFeature : IHttpRequestInputStreamFeature, IHttpRequestPathFeature, IHttpRequestFeature, IRequestBodyPipeFeature, IDisposable
1818
{
1919
private readonly IHttpRequestFeature _other;
2020

2121
private PipeReader? _pipeReader;
2222
private Stream? _bufferedStream;
2323

24+
private string? _pathInfo;
25+
private string? _filePath;
26+
2427
public HttpRequestInputStreamFeature(IHttpRequestFeature other)
2528
{
2629
BufferThreshold = PreBufferRequestStreamAttribute.DefaultBufferThreshold;
@@ -120,10 +123,15 @@ string IHttpRequestFeature.PathBase
120123
set => _other.PathBase = value;
121124
}
122125

123-
string IHttpRequestFeature.Path
126+
public string Path
124127
{
125128
get => _other.Path;
126-
set => _other.Path = value;
129+
set
130+
{
131+
_filePath = null;
132+
_pathInfo = null;
133+
_other.Path = value;
134+
}
127135
}
128136

129137
string IHttpRequestFeature.QueryString
@@ -179,6 +187,34 @@ private void Reset()
179187

180188
private Stream GetBody() => _bufferedStream ?? _other.Body;
181189

190+
void IHttpRequestPathFeature.Rewrite(string filePath, string pathInfo, string? queryString, bool setClientFilePath)
191+
{
192+
_other.QueryString = queryString ?? string.Empty;
193+
194+
if (string.IsNullOrEmpty(pathInfo))
195+
{
196+
Path = filePath;
197+
}
198+
else if (pathInfo.StartsWith('/'))
199+
{
200+
Path = $"{filePath}{pathInfo}";
201+
}
202+
else
203+
{
204+
Path = $"{filePath}/{pathInfo}";
205+
}
206+
207+
// This must be set after setting Path as it will reset the PathInfo and FilePath instances
208+
_pathInfo = pathInfo;
209+
_filePath = filePath;
210+
}
211+
212+
string IHttpRequestPathFeature.PathInfo => _pathInfo ?? string.Empty;
213+
214+
string IHttpRequestPathFeature.FilePath => _filePath ?? Path;
215+
216+
string IHttpRequestPathFeature.RawUrl => _other.RawTarget;
217+
182218
internal static class AspNetCoreTempDirectory
183219
{
184220
private static string? _tempDirectory;
@@ -191,7 +227,7 @@ public static string TempDirectory
191227
{
192228
// Look for folders in the following order.
193229
var temp = Environment.GetEnvironmentVariable("ASPNETCORE_TEMP") ?? // ASPNETCORE_TEMP - User set temporary location.
194-
Path.GetTempPath(); // Fall back.
230+
System.IO.Path.GetTempPath(); // Fall back.
195231

196232
if (!Directory.Exists(temp))
197233
{

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/RegisterAdapterFeaturesMiddleware.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ private static DelegateDisposable RegisterRequestFeatures(HttpContextCore contex
4545
context.Features.Set<IHttpRequestFeature>(inputStreamFeature);
4646
context.Features.Set<IHttpRequestInputStreamFeature>(inputStreamFeature);
4747
context.Features.Set<IRequestBodyPipeFeature>(inputStreamFeature);
48+
context.Features.Set<IHttpRequestPathFeature>(inputStreamFeature);
4849

4950
return new DelegateDisposable(() =>
5051
{
5152
context.Features.Set<IHttpRequestFeature>(existing);
5253
context.Features.Set<IRequestBodyPipeFeature>(existingPipe);
5354
context.Features.Set<IHttpRequestInputStreamFeature>(null);
55+
context.Features.Set<IHttpRequestPathFeature>(null);
5456
});
5557
}
5658

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if NETCOREAPP
5+
6+
internal interface IHttpRequestPathFeature
7+
{
8+
string Path { get; }
9+
10+
string PathInfo { get; }
11+
12+
string FilePath { get; }
13+
14+
string RawUrl { get; }
15+
16+
void Rewrite(string filePath, string pathInfo, string? queryString, bool setClientFilePath);
17+
}
18+
19+
#endif

src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/IPathInfoFeature.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ internal HttpContext() { }
125125
public void ClearError() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
126126
public System.Web.ISubscriptionToken DisposeOnPipelineCompleted(System.IDisposable target) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
127127
public void RewritePath(string path) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
128+
public void RewritePath(string path, bool rebaseClientPath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
129+
public void RewritePath(string filePath, string pathInfo, string queryString) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
130+
public void RewritePath(string filePath, string pathInfo, string queryString, bool setClientFilePath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
128131
object System.IServiceProvider.GetService(System.Type service) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
129132
}
130133
public partial class HttpContextBase : System.IServiceProvider

src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ public IPrincipal User
7676

7777
public DateTime Timestamp { get; } = DateTime.UtcNow.ToLocalTime();
7878

79-
public void RewritePath(string path)
79+
public void RewritePath(string path) => RewritePath(path, true);
80+
81+
public void RewritePath(string path, bool rebaseClientPath)
8082
{
8183
ArgumentNullException.ThrowIfNull(path);
8284

@@ -95,10 +97,15 @@ public void RewritePath(string path)
9597
path = "/" + path;
9698
}
9799

98-
_context.Request.QueryString = new QueryString(qs);
99-
_context.Request.Path = new PathString(path.Trim());
100+
RewritePath(path.Trim(), string.Empty, qs, rebaseClientPath);
100101
}
101102

103+
public void RewritePath(string filePath, string pathInfo, string? queryString)
104+
=> RewritePath(filePath, pathInfo, queryString, false);
105+
106+
public void RewritePath(string filePath, string pathInfo, string? queryString, bool setClientFilePath)
107+
=> _context.Features.GetRequired<IHttpRequestPathFeature>().Rewrite(filePath, pathInfo, queryString, setClientFilePath);
108+
102109
[SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = Constants.ApiFromAspNet)]
103110
object? IServiceProvider.GetService(Type service)
104111
{

src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequest.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ internal HttpRequest(HttpRequestCore request)
4242

4343
internal RequestHeaders TypedHeaders => _typedHeaders ??= new(_request.Headers);
4444

45-
public string? Path => _request.Path.Value;
45+
public string Path => _request.HttpContext.Features.GetRequired<IHttpRequestPathFeature>().Path;
4646

47-
public string? PathInfo => _request.HttpContext.Features.Get<IPathInfoFeature>()?.PathInfo ?? string.Empty;
47+
public string PathInfo => _request.HttpContext.Features.GetRequired<IHttpRequestPathFeature>().PathInfo;
4848

49-
public string? FilePath => _request.HttpContext.Features.Get<IPathInfoFeature>()?.FileInfo ?? Path;
49+
public string FilePath => _request.HttpContext.Features.GetRequired<IHttpRequestPathFeature>().FilePath;
50+
51+
[SuppressMessage("Design", "CA1056:URI-like properties should not be strings", Justification = Constants.ApiFromAspNet)]
52+
public string RawUrl => _request.HttpContext.Features.GetRequired<IHttpRequestPathFeature>().RawUrl;
5053

5154
public NameValueCollection Headers => _headers ??= _request.Headers.ToNameValueCollection();
5255

@@ -58,9 +61,6 @@ internal HttpRequest(HttpRequestCore request)
5861

5962
public Stream GetBufferedInputStream() => _request.HttpContext.Features.GetRequired<IHttpRequestInputStreamFeature>().GetBufferedInputStream();
6063

61-
[SuppressMessage("Design", "CA1056:URI-like properties should not be strings", Justification = Constants.ApiFromAspNet)]
62-
public string? RawUrl => _request.HttpContext.Features.Get<IHttpRequestFeature>()?.RawTarget;
63-
6464
public string HttpMethod => _request.Method;
6565

6666
public string? UserHostAddress => _request.HttpContext.Connection.RemoteIpAddress?.ToString();

src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ public abstract class HttpRequestBase
1616
[Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = Constants.ApiFromAspNet)]
1717
public virtual string[] AcceptTypes => throw new NotImplementedException();
1818

19-
public virtual string? Path => throw new NotImplementedException();
19+
public virtual string Path => throw new NotImplementedException();
2020

2121
public virtual NameValueCollection Headers => throw new NotImplementedException();
2222

2323
public virtual Uri Url => throw new NotImplementedException();
2424

2525
[Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1056:URI-like properties should not be strings", Justification = Constants.ApiFromAspNet)]
26-
public virtual string? RawUrl => throw new NotImplementedException();
26+
public virtual string RawUrl => throw new NotImplementedException();
2727

2828
public virtual string HttpMethod => throw new NotImplementedException();
2929

src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestWrapper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ public override string? ContentType
5757

5858
public override IIdentity? LogonUserIdentity => _request.LogonUserIdentity;
5959

60-
public override string? Path => _request.Path;
60+
public override string Path => _request.Path;
6161

6262
public override NameValueCollection QueryString => _request.QueryString;
6363

6464
public override HttpBrowserCapabilitiesBase Browser => new HttpBrowserCapabilitiesWrapper(_request.Browser);
6565

66-
public override string? RawUrl => _request.RawUrl;
66+
public override string RawUrl => _request.RawUrl;
6767

6868
public override string RequestType => _request.RequestType;
6969

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Builder;
7+
using Microsoft.AspNetCore.Hosting;
8+
using Microsoft.AspNetCore.TestHost;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Hosting;
11+
using Xunit;
12+
13+
namespace Microsoft.AspNetCore.SystemWebAdapters;
14+
15+
public class HttpContextIntegrationTests
16+
{
17+
[Fact]
18+
public Task RequestInfo()
19+
=> RunTest("/", context =>
20+
{
21+
Assert.Equal("/", context.Request.Path);
22+
Assert.Empty(context.Request.Query);
23+
24+
var adapter = (System.Web.HttpRequest)context.Request;
25+
26+
Assert.Equal("", adapter.PathInfo);
27+
Assert.Equal("/", adapter.FilePath);
28+
Assert.Equal("/", adapter.Path);
29+
Assert.Empty(adapter.QueryString);
30+
});
31+
32+
[Fact]
33+
public Task RewriteRequestPath()
34+
=> RunTest("/", context =>
35+
{
36+
var adapter = (System.Web.HttpContext)context;
37+
38+
adapter.RewritePath("/some/path?q=1");
39+
40+
Assert.Equal("/some/path", context.Request.Path);
41+
Assert.Collection(context.Request.Query,
42+
q =>
43+
{
44+
Assert.Equal("q", q.Key);
45+
Assert.Equal("1", q.Value);
46+
});
47+
48+
Assert.Equal("", adapter.Request.PathInfo);
49+
Assert.Equal("/some/path", adapter.Request.FilePath);
50+
Assert.Equal("/some/path", adapter.Request.Path);
51+
Assert.Single(adapter.Request.QueryString);
52+
Assert.Equal("1", adapter.Request.QueryString["q"]);
53+
});
54+
55+
[Fact]
56+
public Task RewriteRequestPathInfo()
57+
=> RunTest("/", context =>
58+
{
59+
var adapter = (System.Web.HttpContext)context;
60+
61+
adapter.RewritePath("/some/path", "/pathInfo", "q=1");
62+
63+
Assert.Equal("/some/path/pathInfo", context.Request.Path);
64+
Assert.Collection(context.Request.Query,
65+
q =>
66+
{
67+
Assert.Equal("q", q.Key);
68+
Assert.Equal("1", q.Value);
69+
});
70+
71+
Assert.Equal("/pathInfo", adapter.Request.PathInfo);
72+
Assert.Equal("/some/path", adapter.Request.FilePath);
73+
Assert.Equal("/some/path/pathInfo", adapter.Request.Path);
74+
Assert.Single(adapter.Request.QueryString);
75+
Assert.Equal("1", adapter.Request.QueryString["q"]);
76+
});
77+
78+
[Fact]
79+
public Task RewritePathViaCoreApis()
80+
=> RunTest("/", context =>
81+
{
82+
var adapter = (System.Web.HttpContext)context;
83+
84+
// This is the same as RewriteRequestPathInfo as above to get a custom PathInfo
85+
adapter.RewritePath("/some/path", "/pathInfo", "q=1");
86+
context.Request.Path = "/other";
87+
88+
Assert.Equal("/other", context.Request.Path);
89+
Assert.Collection(context.Request.Query,
90+
q =>
91+
{
92+
Assert.Equal("q", q.Key);
93+
Assert.Equal("1", q.Value);
94+
});
95+
96+
Assert.Equal(string.Empty, adapter.Request.PathInfo);
97+
Assert.Equal("/other", adapter.Request.FilePath);
98+
Assert.Equal("/other", adapter.Request.Path);
99+
Assert.Single(adapter.Request.QueryString);
100+
Assert.Equal("1", adapter.Request.QueryString["q"]);
101+
});
102+
103+
private static async Task RunTest(string path, Action<HttpContextCore> run)
104+
{
105+
// Arrange
106+
using var host = await new HostBuilder()
107+
.ConfigureWebHost(webBuilder =>
108+
{
109+
webBuilder
110+
.UseTestServer()
111+
.ConfigureServices(services =>
112+
{
113+
services.AddRouting();
114+
services.AddSystemWebAdapters();
115+
})
116+
.Configure(app =>
117+
{
118+
app.UseRouting();
119+
app.UseSystemWebAdapters();
120+
121+
app.Run(ctx =>
122+
{
123+
run(ctx);
124+
return Task.CompletedTask;
125+
});
126+
127+
});
128+
})
129+
.StartAsync();
130+
131+
_ = await host.GetTestClient().GetAsync(new Uri(path, UriKind.Relative));
132+
}
133+
}

0 commit comments

Comments
 (0)