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
56 changes: 41 additions & 15 deletions src/https/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,20 +261,8 @@ public async Task<int> RunAsync(string[] args)
var stdoutWriter = new StreamWriter(stdout) { AutoFlush = true };
{
var renderer = new Renderer(stdoutWriter, stderrWriter);

var http = options.IgnoreCertificate
? new HttpClient(
new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
}
)
: new HttpClient();

if (options.Timeout.HasValue)
{
http.Timeout = options.Timeout.Value;
}
var http = CreateHttpClient(options);

var request = new HttpRequestMessage(
command.Method ?? HttpMethod.Get,
Expand Down Expand Up @@ -341,6 +329,35 @@ public async Task<int> RunAsync(string[] args)

return 0;
}

static HttpClient CreateHttpClient(Options options)
{
var http = default(HttpClient);
if (options.RequiresHandler)
{
var handler = new HttpClientHandler();
if (options.IgnoreCertificate)
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
if (options.StopAutoRedirects)
{
handler.AllowAutoRedirect = false;
}
http = new HttpClient(handler);
}
else
{
http = new HttpClient();
}

if (options.Timeout.HasValue)
{
http.Timeout = options.Timeout.Value;
}

return http;
}
}

enum ContentType
Expand All @@ -358,16 +375,19 @@ class Options
public TimeSpan? Timeout { get; }
public bool Version { get; }
public bool Help { get; }
public bool StopAutoRedirects { get; }

public bool RequiresHandler => IgnoreCertificate || StopAutoRedirects;

public Options(ContentType requestContentType, string xmlRootName, bool ignoreCertificate, TimeSpan? timeout, bool version, bool help)
public Options(ContentType requestContentType, string xmlRootName, bool ignoreCertificate, TimeSpan? timeout, bool version, bool help, bool stopAutoRedirects)
{
RequestContentType = requestContentType;
XmlRootName = xmlRootName;
IgnoreCertificate = ignoreCertificate;
Timeout = timeout;
Version = version;
Help = help;
StopAutoRedirects = stopAutoRedirects;
}

public static IEnumerable<string> GetOptionHelp()
Expand All @@ -379,6 +399,7 @@ public static IEnumerable<string> GetOptionHelp()
yield return "--timeout=<VALUE> Sets the timeout of the request using System.TimeSpan.TryParse (https://docs.microsoft.com/en-us/dotnet/api/system.timespan.parse)";
yield return "--version Displays the application verison.";
yield return "--xml=<ROOT_NAME> Renders the content arguments as application/xml using the optional xml root name.";
yield return "--stop-auto-redirects Prevents redirects from automatically being processed.";
}

static int GetArgValueIndex(string arg)
Expand All @@ -400,6 +421,7 @@ public static Options Parse(IEnumerable<string> args)
var timeout = default(TimeSpan?);
var help = false;
var version = false;
var stopAutoRedirects = false;
foreach (var arg in args)
{
if (arg.StartsWith("--json"))
Expand Down Expand Up @@ -451,8 +473,12 @@ public static Options Parse(IEnumerable<string> args)
{
help = true;
}
else if (arg.StartsWith("--stop-auto-redirects"))
{
stopAutoRedirects = true;
}
}
return new Options(requestContentType, xmlRootName, ignoreCertificate, timeout, version, help);
return new Options(requestContentType, xmlRootName, ignoreCertificate, timeout, version, help, stopAutoRedirects);
}
}

Expand Down
26 changes: 26 additions & 0 deletions tests/https.Tests/Https.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.IO;
using System.Threading.Tasks;

namespace Https.Tests
{
public static class Https
{
public static async Task<HttpsResult> ExecuteAsync(params string[] args)
{
using var stdin = new MemoryStream();

return await ExecuteAsync(stdin, args);
}

public static async Task<HttpsResult> ExecuteAsync(Stream stdin, params string[] args)
{
var stdout = new MemoryStream();
var stderr = new MemoryStream();

var exitCode = await new Program(() => stderr, () => stdin, () => stdout, false)
.RunAsync(args);

return new HttpsResult(exitCode, stdout, stderr);
}
}
}
50 changes: 50 additions & 0 deletions tests/https.Tests/HttpsResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Https.Tests
{
public class HttpsResult : IDisposable
{
public int ExitCode { get; }
public MemoryStream StdOut { get; }
public string Status { get; }
public IReadOnlyDictionary<string, string> Headers { get; }

public HttpsResult(int exitCode, MemoryStream stdout, MemoryStream stderr)
{
ExitCode = exitCode;

StdOut = stdout;
StdOut.Position = 0;

stderr.Position = 0;
var lines = new StreamReader(stderr)
.ReadToEnd()
.Split(Environment.NewLine);

Status = lines[0];

var headers = new Dictionary<string, string>();
foreach (var line in lines.Skip(1))
{
var pos = line.IndexOf(':');
if (pos > -1)
{
var key = line.Substring(0, pos);
var value = line.Substring(pos + 2);
headers[key] = value;
}
}
Headers = headers;

stderr.Dispose();
}

public void Dispose()
{
StdOut.Dispose();
}
}
}
41 changes: 41 additions & 0 deletions tests/https.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.IO;
using System.Threading.Tasks;
using Xunit;

namespace Https.Tests
{
public class IntegrationTests : IClassFixture<WebHostFixture>
{
readonly WebHostFixture _fixture;
public IntegrationTests(WebHostFixture fixture) =>
_fixture = fixture;

[Fact]
public async Task MirrorTests()
{
var args = new[]
{
"post", $"{_fixture.Url}/Mirror", "--json", "foo=bar", "lorem=ipsum"
};

var result = await Https.ExecuteAsync(args);

var json = new StreamReader(result.StdOut).ReadToEnd();
Assert.Equal("{\"foo\":\"bar\",\"lorem\":\"ipsum\"}", json);
}

[Fact]
public async Task RedirectTest_ShouldShow3XXResponse_GivenStopAutoRedirects()
{
var args = new[]
{
"get", "http://localhost:5000/Redirect", "--stop-auto-redirects"
};

var result = await Https.ExecuteAsync(args);

Assert.Equal("HTTP/1.1 301 Moved Permanently", result.Status);
Assert.Equal("http://localhost:5000/Mirror", result.Headers["Location"]);
}
}
}
28 changes: 28 additions & 0 deletions tests/https.Tests/MirrorMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace Https.Tests
{
class MirrorMiddleware
{
readonly RequestDelegate _next;
public MirrorMiddleware(RequestDelegate next) =>
_next = next;
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/Mirror"))
{
if (!string.IsNullOrEmpty(context.Request.ContentType))
{
context.Response.ContentType = context.Request.ContentType;
}

await context.Request.Body.CopyToAsync(context.Response.Body);
}
else
{
await _next(context);
}
}
}
}
24 changes: 24 additions & 0 deletions tests/https.Tests/RedirectMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace Https.Tests
{
class RedirectMiddleware
{
readonly RequestDelegate _next;
public RedirectMiddleware(RequestDelegate next) =>
_next = next;
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/Redirect"))
{
context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Response.Headers.Add("Location", "http://localhost:5000/Mirror");
}
else
{
await _next(context);
}
}
}
}
20 changes: 20 additions & 0 deletions tests/https.Tests/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

namespace Https.Tests
{
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<RedirectMiddleware>();
app.UseMiddleware<MirrorMiddleware>();

app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello!");
});
}
}
}
33 changes: 33 additions & 0 deletions tests/https.Tests/WebHostFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Https.Tests
{
public class WebHostFixture : IDisposable
{
readonly IWebHost _webHost;
readonly Task _task;
readonly CancellationTokenSource _cts;
public string Url { get; }

public WebHostFixture()
{
_webHost = WebHost.CreateDefaultBuilder()
.UseUrls(Url = "http://localhost:5000")
.UseStartup<Startup>()
.Build();
_cts = new CancellationTokenSource();
_task = _webHost.RunAsync(_cts.Token);
}

public async void Dispose()
{
await _webHost.StopAsync();
_cts.Cancel();
_cts.Dispose();
}
}
}