Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@

namespace Microsoft.AspNetCore.SystemWebAdapters;

internal class HttpRequestInputStreamFeature : IHttpRequestInputStreamFeature, IHttpRequestFeature, IRequestBodyPipeFeature, IDisposable
internal class HttpRequestInputStreamFeature : IHttpRequestInputStreamFeature, IHttpRequestPathFeature, IHttpRequestFeature, IRequestBodyPipeFeature, IDisposable
{
private readonly IHttpRequestFeature _other;

private PipeReader? _pipeReader;
private Stream? _bufferedStream;

private string? _pathInfo;
private string? _filePath;

public HttpRequestInputStreamFeature(IHttpRequestFeature other)
{
BufferThreshold = PreBufferRequestStreamAttribute.DefaultBufferThreshold;
Expand Down Expand Up @@ -120,10 +123,15 @@ string IHttpRequestFeature.PathBase
set => _other.PathBase = value;
}

string IHttpRequestFeature.Path
public string Path
{
get => _other.Path;
set => _other.Path = value;
set
{
_filePath = null;
_pathInfo = null;
_other.Path = value;
}
}

string IHttpRequestFeature.QueryString
Expand Down Expand Up @@ -179,6 +187,34 @@ private void Reset()

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

void IHttpRequestPathFeature.Rewrite(string filePath, string pathInfo, string? queryString, bool setClientFilePath)
{
_other.QueryString = queryString ?? string.Empty;

if (string.IsNullOrEmpty(pathInfo))
{
Path = filePath;
}
else if (pathInfo.StartsWith('/'))
{
Path = $"{filePath}{pathInfo}";
}
else
{
Path = $"{filePath}/{pathInfo}";
}

// This must be set after setting Path as it will reset the PathInfo and FilePath instances
_pathInfo = pathInfo;
_filePath = filePath;
}

string IHttpRequestPathFeature.PathInfo => _pathInfo ?? string.Empty;

string IHttpRequestPathFeature.FilePath => _filePath ?? Path;

string IHttpRequestPathFeature.RawUrl => _other.RawTarget;

internal static class AspNetCoreTempDirectory
{
private static string? _tempDirectory;
Expand All @@ -191,7 +227,7 @@ public static string TempDirectory
{
// Look for folders in the following order.
var temp = Environment.GetEnvironmentVariable("ASPNETCORE_TEMP") ?? // ASPNETCORE_TEMP - User set temporary location.
Path.GetTempPath(); // Fall back.
System.IO.Path.GetTempPath(); // Fall back.

if (!Directory.Exists(temp))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ private static DelegateDisposable RegisterRequestFeatures(HttpContextCore contex
context.Features.Set<IHttpRequestFeature>(inputStreamFeature);
context.Features.Set<IHttpRequestInputStreamFeature>(inputStreamFeature);
context.Features.Set<IRequestBodyPipeFeature>(inputStreamFeature);
context.Features.Set<IHttpRequestPathFeature>(inputStreamFeature);

return new DelegateDisposable(() =>
{
context.Features.Set<IHttpRequestFeature>(existing);
context.Features.Set<IRequestBodyPipeFeature>(existingPipe);
context.Features.Set<IHttpRequestInputStreamFeature>(null);
context.Features.Set<IHttpRequestPathFeature>(null);
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NETCOREAPP

internal interface IHttpRequestPathFeature
{
string Path { get; }

string PathInfo { get; }

string FilePath { get; }

string RawUrl { get; }

void Rewrite(string filePath, string pathInfo, string? queryString, bool setClientFilePath);
}

#endif

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ internal HttpContext() { }
public void ClearError() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public System.Web.ISubscriptionToken DisposeOnPipelineCompleted(System.IDisposable target) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void RewritePath(string path) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void RewritePath(string path, bool rebaseClientPath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void RewritePath(string filePath, string pathInfo, string queryString) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
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");}
object System.IServiceProvider.GetService(System.Type service) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
}
public partial class HttpContextBase : System.IServiceProvider
Expand Down
13 changes: 10 additions & 3 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ public IPrincipal User

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

public void RewritePath(string path)
public void RewritePath(string path) => RewritePath(path, true);

public void RewritePath(string path, bool rebaseClientPath)
{
ArgumentNullException.ThrowIfNull(path);

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

_context.Request.QueryString = new QueryString(qs);
_context.Request.Path = new PathString(path.Trim());
RewritePath(path.Trim(), string.Empty, qs, rebaseClientPath);
}

public void RewritePath(string filePath, string pathInfo, string? queryString)
=> RewritePath(filePath, pathInfo, queryString, false);

public void RewritePath(string filePath, string pathInfo, string? queryString, bool setClientFilePath)
=> _context.Features.GetRequired<IHttpRequestPathFeature>().Rewrite(filePath, pathInfo, queryString, setClientFilePath);

[SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = Constants.ApiFromAspNet)]
object? IServiceProvider.GetService(Type service)
{
Expand Down
12 changes: 6 additions & 6 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ internal HttpRequest(HttpRequestCore request)

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

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

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

public string? FilePath => _request.HttpContext.Features.Get<IPathInfoFeature>()?.FileInfo ?? Path;
public string FilePath => _request.HttpContext.Features.GetRequired<IHttpRequestPathFeature>().FilePath;

[SuppressMessage("Design", "CA1056:URI-like properties should not be strings", Justification = Constants.ApiFromAspNet)]
public string RawUrl => _request.HttpContext.Features.GetRequired<IHttpRequestPathFeature>().RawUrl;

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

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

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

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

public string HttpMethod => _request.Method;

public string? UserHostAddress => _request.HttpContext.Connection.RemoteIpAddress?.ToString();
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ public abstract class HttpRequestBase
[Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = Constants.ApiFromAspNet)]
public virtual string[] AcceptTypes => throw new NotImplementedException();

public virtual string? Path => throw new NotImplementedException();
public virtual string Path => throw new NotImplementedException();

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

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ public override string? ContentType

public override IIdentity? LogonUserIdentity => _request.LogonUserIdentity;

public override string? Path => _request.Path;
public override string Path => _request.Path;

public override NameValueCollection QueryString => _request.QueryString;

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

public override string? RawUrl => _request.RawUrl;
public override string RawUrl => _request.RawUrl;

public override string RequestType => _request.RequestType;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;

namespace Microsoft.AspNetCore.SystemWebAdapters;

public class HttpContextIntegrationTests
{
[Fact]
public Task RequestInfo()
=> RunTest("/", context =>
{
Assert.Equal("/", context.Request.Path);
Assert.Empty(context.Request.Query);

var adapter = (System.Web.HttpRequest)context.Request;

Assert.Equal("", adapter.PathInfo);
Assert.Equal("/", adapter.FilePath);
Assert.Equal("/", adapter.Path);
Assert.Empty(adapter.QueryString);
});

[Fact]
public Task RewriteRequestPath()
=> RunTest("/", context =>
{
var adapter = (System.Web.HttpContext)context;

adapter.RewritePath("/some/path?q=1");

Assert.Equal("/some/path", context.Request.Path);
Assert.Collection(context.Request.Query,
q =>
{
Assert.Equal("q", q.Key);
Assert.Equal("1", q.Value);
});

Assert.Equal("", adapter.Request.PathInfo);
Assert.Equal("/some/path", adapter.Request.FilePath);
Assert.Equal("/some/path", adapter.Request.Path);
Assert.Single(adapter.Request.QueryString);
Assert.Equal("1", adapter.Request.QueryString["q"]);
});

[Fact]
public Task RewriteRequestPathInfo()
=> RunTest("/", context =>
{
var adapter = (System.Web.HttpContext)context;

adapter.RewritePath("/some/path", "/pathInfo", "q=1");

Assert.Equal("/some/path/pathInfo", context.Request.Path);
Assert.Collection(context.Request.Query,
q =>
{
Assert.Equal("q", q.Key);
Assert.Equal("1", q.Value);
});

Assert.Equal("/pathInfo", adapter.Request.PathInfo);
Assert.Equal("/some/path", adapter.Request.FilePath);
Assert.Equal("/some/path/pathInfo", adapter.Request.Path);
Assert.Single(adapter.Request.QueryString);
Assert.Equal("1", adapter.Request.QueryString["q"]);
});

[Fact]
public Task RewritePathViaCoreApis()
=> RunTest("/", context =>
{
var adapter = (System.Web.HttpContext)context;

// This is the same as RewriteRequestPathInfo as above to get a custom PathInfo
adapter.RewritePath("/some/path", "/pathInfo", "q=1");
context.Request.Path = "/other";

Assert.Equal("/other", context.Request.Path);
Assert.Collection(context.Request.Query,
q =>
{
Assert.Equal("q", q.Key);
Assert.Equal("1", q.Value);
});

Assert.Equal(string.Empty, adapter.Request.PathInfo);
Assert.Equal("/other", adapter.Request.FilePath);
Assert.Equal("/other", adapter.Request.Path);
Assert.Single(adapter.Request.QueryString);
Assert.Equal("1", adapter.Request.QueryString["q"]);
});

private static async Task RunTest(string path, Action<HttpContextCore> run)
{
// Arrange
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddRouting();
services.AddSystemWebAdapters();
})
.Configure(app =>
{
app.UseRouting();
app.UseSystemWebAdapters();

app.Run(ctx =>
{
run(ctx);
return Task.CompletedTask;
});

});
})
.StartAsync();

_ = await host.GetTestClient().GetAsync(new Uri(path, UriKind.Relative));
}
}
Loading