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 @@ -267,6 +267,7 @@ internal HttpResponse() { }
public bool IsRequestBeingRedirected { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public System.IO.TextWriter Output { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public System.IO.Stream OutputStream { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string RedirectLocation { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public int StatusCode { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string StatusDescription { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public int SubStatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
Expand All @@ -280,6 +281,8 @@ internal HttpResponse() { }
public void ClearContent() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void ClearHeaders() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void End() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void Redirect(string url) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void Redirect(string url, bool endResponse) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void RedirectPermanent(string url) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void RedirectPermanent(string url, bool endResponse) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void SetCookie(System.Web.HttpCookie cookie) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
Expand Down
61 changes: 56 additions & 5 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.AspNetCore.SystemWebAdapters;
using Microsoft.AspNetCore.SystemWebAdapters.Internal;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;

namespace System.Web
Expand Down Expand Up @@ -181,24 +182,74 @@ public void AppendHeader(string name, string value)
}
}

public string RedirectLocation
{
get => _response.Headers.Location;
set => _response.Headers.Location = value;
}

public bool IsRequestBeingRedirected => StatusCode is >= 300 and < 400;

[SuppressMessage("Design", "CA1054:URI parameters should not be strings", Justification = "_writer is registered to be disposed by the owning HttpContext")]
public void RedirectPermanent(string url) => Redirect(url, true, true);
[SuppressMessage("Design", "CA1054:URI parameters should not be strings", Justification = Constants.ApiFromAspNet)]
public void Redirect(string url) => Redirect(url, endResponse: true, permanent: false);

[SuppressMessage("Design", "CA1054:URI parameters should not be strings", Justification = "_writer is registered to be disposed by the owning HttpContext")]
public void RedirectPermanent(string url, bool endResponse) => Redirect(url, endResponse, true);
[SuppressMessage("Design", "CA1054:URI parameters should not be strings", Justification = Constants.ApiFromAspNet)]
public void Redirect(string url, bool endResponse) => Redirect(url, endResponse, permanent: false);

[SuppressMessage("Design", "CA1054:URI parameters should not be strings", Justification = Constants.ApiFromAspNet)]
public void RedirectPermanent(string url) => Redirect(url, endResponse: true, permanent: true);

[SuppressMessage("Design", "CA1054:URI parameters should not be strings", Justification = Constants.ApiFromAspNet)]
public void RedirectPermanent(string url, bool endResponse) => Redirect(url, endResponse, permanent: true);

private void Redirect(string url, bool endResponse, bool permanent)
{
_response.Redirect(url, permanent);
Clear();

var resolved = ResolvePath(url);
_response.Redirect(resolved, permanent);

ContentType = "text/html";

Output.WriteLine("<html><head><title>Object moved</title></head><body>");
Output.Write("<h2>Object moved to <a href=\"");
Output.Write(resolved);
Output.WriteLine("\">here</a>.</h2>");
Output.WriteLine("</body></html>");

if (endResponse)
{
End();
}
}

private string ResolvePath(string url)
{
if (string.IsNullOrEmpty(url))
{
return "/";
}

if (!url.StartsWith('~'))
{
return url;
}

var vdir = _response.HttpContext.RequestServices.GetRequiredService<IHttpRuntime>().AppDomainAppVirtualPath;

var sb = new StringBuilder(url, 1, url.Length - 1, url.Length + vdir.Length);

if (sb.Length == 0 || sb[0] != '/')
{
sb.Insert(0, '/');
}

sb.Insert(0, vdir);
sb.Replace("//", "/");

return sb.ToString();
}

public void SetCookie(HttpCookie cookie) => Cookies.Set(cookie);

public void End() => AdapterFeature.EndAsync().GetAwaiter().GetResult();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,42 +479,101 @@ public void IsRequestBeingRedirected(int statusCode, bool isRequestBeingRedirect
Assert.Equal(isRequestBeingRedirected, result);
}

[InlineData(null)]
[InlineData(false)]
[InlineData(true)]
[InlineData("/", "~", "/", true, null)]
[InlineData("/", "~", "/", true, false)]
[InlineData("/", "~", "/", true, true)]
[InlineData("/", "~", "/", false, null)]
[InlineData("/", "~", "/", false, false)]
[InlineData("/", "~", "/", false, true)]

[InlineData("/", "~/dir", "/dir", true, null)]
[InlineData("/", "~/dir", "/dir", true, false)]
[InlineData("/", "~/dir", "/dir", true, true)]
[InlineData("/", "~/dir", "/dir", false, null)]
[InlineData("/", "~/dir", "/dir", false, false)]
[InlineData("/", "~/dir", "/dir", false, true)]

[InlineData("/", "/dir", "/dir", true, null)]
[InlineData("/", "/dir", "/dir", true, false)]
[InlineData("/", "/dir", "/dir", true, true)]
[InlineData("/", "/dir", "/dir", false, null)]
[InlineData("/", "/dir", "/dir", false, false)]
[InlineData("/", "/dir", "/dir", false, true)]

[InlineData("/dir1/", "/dir2", "/dir2", true, null)]
[InlineData("/dir1/", "/dir2", "/dir2", true, false)]
[InlineData("/dir1/", "/dir2", "/dir2", true, true)]
[InlineData("/dir1/", "/dir2", "/dir2", false, null)]
[InlineData("/dir1/", "/dir2", "/dir2", false, false)]
[InlineData("/dir1/", "/dir2", "/dir2", false, true)]

[InlineData("/dir1/", "~/dir2", "/dir1/dir2", true, null)]
[InlineData("/dir1/", "~/dir2", "/dir1/dir2", true, false)]
[InlineData("/dir1/", "~/dir2", "/dir1/dir2", true, true)]
[InlineData("/dir1/", "~/dir2", "/dir1/dir2", false, null)]
[InlineData("/dir1/", "~/dir2", "/dir1/dir2", false, false)]
[InlineData("/dir1/", "~/dir2", "/dir1/dir2", false, true)]

[InlineData("/dir1/", "", "/", true, null)]
[InlineData("/dir1/", "", "/", true, false)]
[InlineData("/dir1/", "", "/", true, true)]
[InlineData("/dir1/", "", "/", false, null)]
[InlineData("/dir1/", "", "/", false, false)]
[InlineData("/dir1/", "", "/", false, true)]

[Theory]
public void RedirectPermanent(bool? endResponse)
public void Redirect(string vdir, string url, string resolved, bool permanent, bool? endResponse)
{
// Arrange
var isEndCalled = endResponse ?? true;
var url = _fixture.Create<string>();
var features = new FeatureCollection();

var bufferedBody = new Mock<IHttpResponseAdapterFeature>();
bufferedBody.SetupAllProperties();
features.Set(bufferedBody.Object);
var runtime = new Mock<IHttpRuntime>();
runtime.Setup(r => r.AppDomainAppVirtualPath).Returns(vdir);

var context = new Mock<HttpContextCore>();
context.Setup(c => c.Features).Returns(features);
var services = new Mock<IServiceProvider>();
services.Setup(s => s.GetService(typeof(IHttpRuntime))).Returns(runtime.Object);

var responseCore = new Mock<HttpResponseCore>();
responseCore.SetupProperty(r => r.StatusCode);
responseCore.Setup(r => r.HttpContext).Returns(context.Object);
var adapterFeatures = new Mock<IHttpResponseAdapterFeature>();
adapterFeatures.SetupAllProperties();

var response = new HttpResponse(responseCore.Object);
var context = new DefaultHttpContext();
context.Features.Set(adapterFeatures.Object);
context.RequestServices = services.Object;

var response = new HttpResponse(context.Response);

// Act
if (endResponse.HasValue)
{
response.RedirectPermanent(url, endResponse.Value);
if (permanent)
{
response.RedirectPermanent(url, endResponse.Value);
}
else
{
response.Redirect(url, endResponse.Value);
}
}
else
{
response.RedirectPermanent(url);
if (permanent)
{
response.RedirectPermanent(url);
}
else
{
response.Redirect(url);
}
}

// Assert
responseCore.Verify(r => r.Redirect(url, true), Times.Once);
bufferedBody.Verify(b => b.EndAsync(), isEndCalled ? Times.Once : Times.Never);
Assert.Equal(resolved, response.RedirectLocation);
Assert.Null(context.Features.GetRequired<IHttpResponseFeature>().ReasonPhrase);
Assert.Equal(2, context.Response.Headers.Count);
Assert.Equal(resolved, context.Response.Headers.Location);
Assert.Equal("text/html", context.Response.Headers.ContentType);
Assert.Equal(permanent ? 301 : 302, context.Response.StatusCode);

adapterFeatures.Verify(b => b.EndAsync(), isEndCalled ? Times.Once : Times.Never);
}
}