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
24 changes: 24 additions & 0 deletions DevProxy.Abstractions.Tests/Proxy/SystemProxyAddressTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class SystemProxyAddressTests
[InlineData(" ")]
[InlineData("0.0.0.0")]
[InlineData("::")]
[InlineData("::0")]
[InlineData("0:0:0:0:0:0:0:0")]
[InlineData("[::]")]
public void ResolveHost_WildcardOrEmpty_CollapsesToLoopback(string? ipAddress) =>
Assert.Equal("127.0.0.1", SystemProxyAddress.ResolveHost(ipAddress));

Expand All @@ -33,6 +36,27 @@ public void ToHostPort_ComposesNormalizedHostAndPort() =>
public void ToHostPort_ExplicitAddress() =>
Assert.Equal("10.0.0.5:9090", SystemProxyAddress.ToHostPort("10.0.0.5", 9090));

[Fact]
public void ToHostPort_ExplicitIpv6Address_UsesBrackets() =>
Assert.Equal("[::1]:9090", SystemProxyAddress.ToHostPort("::1", 9090));

[Fact]
public void ToHttpAuthority_Ipv6Address_UsesBrackets() =>
Assert.Equal("http://[::1]:9090", SystemProxyAddress.ToHttpAuthority("::1", 9090));

[Fact]
public void ToHttpAuthority_DefaultHttpPort_IsPreserved() =>
Assert.Equal("http://127.0.0.1:80", SystemProxyAddress.ToHttpAuthority("127.0.0.1", 80));

[Theory]
[InlineData("0.0.0.0")]
[InlineData("::")]
[InlineData("::0")]
[InlineData("0:0:0:0:0:0:0:0")]
[InlineData("[::]")]
public void ToHttpAuthority_WildcardAddress_UsesLoopback(string ipAddress) =>
Assert.Equal("http://127.0.0.1:9090", SystemProxyAddress.ToHttpAuthority(ipAddress, 9090));

[Fact]
public void ToHostPort_NullAddress_UsesLoopback() =>
Assert.Equal("127.0.0.1:8080", SystemProxyAddress.ToHostPort(null, 8080));
Expand Down
31 changes: 28 additions & 3 deletions DevProxy.Abstractions/Proxy/SystemProxyAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Net;
using System.Net.Sockets;

namespace DevProxy.Abstractions.Proxy;

Expand Down Expand Up @@ -33,12 +35,35 @@ public static string ResolveHost(string? ipAddress)
return "127.0.0.1";
}

return ipAddress is "0.0.0.0" or "::" ? "127.0.0.1" : ipAddress;
var host = ipAddress;
if (host.Length > 1 && host[0] == '[' && host[^1] == ']')
{
host = host[1..^1];
}

if (IPAddress.TryParse(host, out var address) &&
(address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)))
{
return "127.0.0.1";
}

return host;
}

public static string ToHttpAuthority(string? ipAddress, int port) =>
$"{Uri.UriSchemeHttp}://{ToHostPort(ipAddress, port)}";

/// <summary>
/// The <c>host:port</c> value used for the Windows <c>ProxyServer</c> registry setting.
/// </summary>
public static string ToHostPort(string? ipAddress, int port) =>
$"{ResolveHost(ipAddress)}:{port.ToString(CultureInfo.InvariantCulture)}";
public static string ToHostPort(string? ipAddress, int port)
{
var host = ResolveHost(ipAddress);
if (IPAddress.TryParse(host, out var address) && address.AddressFamily == AddressFamily.InterNetworkV6)
{
host = $"[{host}]";
}

return $"{host}:{port.ToString(CultureInfo.InvariantCulture)}";
}
}
2 changes: 1 addition & 1 deletion DevProxy.Plugins/Inspection/DevToolsPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ private string GetBrowserPath()
PreferredBrowser.Chrome => "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
PreferredBrowser.Edge => "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
PreferredBrowser.EdgeDev => "/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev",
PreferredBrowser.EdgeBeta => "/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Beta",
PreferredBrowser.EdgeBeta => "/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta",
_ => throw new NotSupportedException($"{Configuration.PreferredBrowser} is an unsupported browser. Please change your PreferredBrowser setting for {Name}.")
};
}
Expand Down
2 changes: 1 addition & 1 deletion DevProxy.Plugins/Mocking/MockStdioResponsePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public sealed class MockStdioResponseConfiguration
public bool NoMocks { get; set; }

[JsonPropertyName("$schema")]
public string Schema { get; set; } = "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v2.1.0/mockstdioresponseplugin.mocksfile.schema.json";
public string Schema { get; set; } = "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v4.0.0/mockstdioresponseplugin.mocksfile.schema.json";
}

public class MockStdioResponsePlugin(
Expand Down
24 changes: 22 additions & 2 deletions DevProxy.Tests/ConsoleHotkeyHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ namespace DevProxy.Tests;
public sealed class ConsoleHotkeyHandlerTests
{
private static (ConsoleHotkeyHandler handler, FakeProxyStateController controller, RecordingConsole console)
CreateHandler(OutputFormat output = OutputFormat.Text)
CreateHandler(OutputFormat output = OutputFormat.Text, string ipAddress = "127.0.0.1")
{
var controller = new FakeProxyStateController();
var console = new RecordingConsole();
var configuration = new FakeProxyConfiguration { Output = output };
var configuration = new FakeProxyConfiguration { Output = output, IPAddress = ipAddress };
var handler = new ConsoleHotkeyHandler(controller, configuration, console);
return (handler, controller, console);
}
Expand Down Expand Up @@ -125,6 +125,26 @@ public void PrintApiInstructions_WritesAllApiCommands()
Assert.Contains("/proxy/stopProxy", joined, StringComparison.Ordinal);
}

[Fact]
public void PrintApiInstructions_FormatsIpv6Address()
{
var (handler, _, console) = CreateHandler(OutputFormat.Json, "::1");

handler.PrintApiInstructions();

Assert.Contains(console.Lines, line => line.Contains("http://[::1]:8897/proxy", StringComparison.Ordinal));
}

[Fact]
public void PrintApiInstructions_NormalizesIpv6WildcardAddress()
{
var (handler, _, console) = CreateHandler(OutputFormat.Json, "::");

handler.PrintApiInstructions();

Assert.Contains(console.Lines, line => line.Contains("http://127.0.0.1:8897/proxy", StringComparison.Ordinal));
}

[Fact]
public void PrintBanner_TextMode_PrintsHotkeys()
{
Expand Down
2 changes: 1 addition & 1 deletion DevProxy/Commands/ApiCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ private void PrintApiInfo(OutputFormat outputFormat)
{
var ipAddress = _proxyConfiguration.IPAddress;
var apiPort = _proxyConfiguration.ApiPort;
var baseUrl = $"http://{ipAddress}:{apiPort}";
var baseUrl = SystemProxyAddress.ToHttpAuthority(ipAddress, apiPort);

var endpoints = new[]
{
Expand Down
7 changes: 5 additions & 2 deletions DevProxy/Commands/DevProxyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public DevProxyCommand(
IProxyConfiguration proxyConfiguration,
IServiceProvider serviceProvider,
UpdateNotification updateNotification,
ILogger<DevProxyCommand> logger) : base($"Start Dev Proxy\n\nAPI:\n Dev Proxy exposes a REST API for runtime management.\n OpenAPI spec: http://{proxyConfiguration.IPAddress ?? "127.0.0.1"}:{proxyConfiguration.ApiPort}/swagger\n Use --api-port to configure (default: {proxyConfiguration.ApiPort}).\n Run 'devproxy api show' for more information.")
ILogger<DevProxyCommand> logger) : base($"Start Dev Proxy\n\nAPI:\n Dev Proxy exposes a REST API for runtime management.\n OpenAPI spec: {SystemProxyAddress.ToHttpAuthority(proxyConfiguration.IPAddress, proxyConfiguration.ApiPort)}/swagger\n Use --api-port to configure (default: {proxyConfiguration.ApiPort}).\n Run 'devproxy api show' for more information.")
{
_serviceProvider = serviceProvider;
_plugins = plugins;
Expand Down Expand Up @@ -313,7 +313,10 @@ private async Task<int> InvokeAsync(ParseResult parseResult, CancellationToken c
_app.Lifetime.ApplicationStarted.Register(() =>
{
var serverAddresses = _app.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>();
var address = serverAddresses?.Addresses.FirstOrDefault() ?? $"http://{_proxyConfiguration.IPAddress}:{_proxyConfiguration.ApiPort}";
var serverAddress = serverAddresses?.Addresses.FirstOrDefault();
var address = Uri.TryCreate(serverAddress, UriKind.Absolute, out var serverUri) ?
SystemProxyAddress.ToHttpAuthority(serverUri.DnsSafeHost, serverUri.Port) :
SystemProxyAddress.ToHttpAuthority(_proxyConfiguration.IPAddress, _proxyConfiguration.ApiPort);
_logger.LogInformation("Dev Proxy API listening on {Address}...", address);

// Persist the daemon state so the parent process's readiness check,
Expand Down
2 changes: 1 addition & 1 deletion DevProxy/Proxy/ConsoleHotkeyHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void PrintHotkeys()

public void PrintApiInstructions()
{
var baseUrl = $"http://{configuration.IPAddress}:{configuration.ApiPort}/proxy";
var baseUrl = SystemProxyAddress.ToHttpAuthority(configuration.IPAddress, configuration.ApiPort) + "/proxy";
var timestamp = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture);
console.WriteLine("");
console.WriteLine($"{{\"type\":\"log\",\"level\":\"info\",\"message\":\"Issue web request: curl -X POST {baseUrl}/mockRequest\",\"category\":\"ProxyEngine\",\"timestamp\":\"{timestamp}\"}}");
Expand Down
2 changes: 1 addition & 1 deletion DevProxy/config-templates/devproxyrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.0/rc.schema.json",
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v4.0.0/rc.schema.json",
"plugins": [
],
"urlsToWatch": [
Expand Down
2 changes: 1 addition & 1 deletion DevProxy/config-templates/devproxyrc.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.0/rc.schema.json
# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v4.0.0/rc.schema.json
plugins:
-
urlsToWatch:
Expand Down
2 changes: 1 addition & 1 deletion DevProxy/devproxy-errors.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.0/genericrandomerrorplugin.errorsfile.schema.json
# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v4.0.0/genericrandomerrorplugin.errorsfile.schema.json
# Dev Proxy error responses configuration
# This YAML version serves as a reference example alongside the default JSON configuration.
# It demonstrates YAML features like comments, multiline strings, and anchors.
Expand Down
4 changes: 2 additions & 2 deletions DevProxy/devproxyrc.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.0/rc.schema.json
# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v4.0.0/rc.schema.json
# Dev Proxy configuration file
# This YAML version serves as a reference example alongside the default JSON configuration.

Expand All @@ -16,7 +16,7 @@ urlsToWatch:
- "https://jsonplaceholder.typicode.com/*"

genericRandomErrorPlugin:
# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.0/genericrandomerrorplugin.schema.json
# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v4.0.0/genericrandomerrorplugin.schema.json
errorsFile: devproxy-errors.yaml
rate: 50

Expand Down
3 changes: 2 additions & 1 deletion schemas/v4.0.0/crudapiplugin.apifile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"POST",
"PUT",
"PATCH",
"DELETE"
"DELETE",
"QUERY"
],
"description": "HTTP method that Dev Proxy uses to expose the action. Defaults depend on the action type."
},
Expand Down
9 changes: 7 additions & 2 deletions schemas/v4.0.0/devtoolsplugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@
"enum": [
"Edge",
"EdgeDev",
"Chrome"
"Chrome",
"EdgeBeta"
Comment thread
waldekmastykarz marked this conversation as resolved.
],
"description": "Which browser to use to launch Dev Tools. Supported values: Edge, EdgeDev, Chrome. Default: Edge."
"description": "Which browser to use to launch Dev Tools. Supported values: Edge, EdgeDev, Chrome, EdgeBeta. Default: Edge."
},
"preferredBrowserPath": {
"type": "string",
"description": "Path to the browser executable. When specified, overrides preferredBrowser."
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"number",
"boolean"
],
"description": "Response body to return."
"description": "Response body to return. Can be an object, array, string, number, or boolean."
},
"statusCode": {
"type": "integer",
Expand Down
2 changes: 1 addition & 1 deletion schemas/v4.0.0/genericrandomerrorplugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"description": "Path to the file that contains error responses."
},
"rate": {
"type": "number",
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "The percentage of requests to fail with a random error. Value between 0 and 100."
Expand Down
2 changes: 1 addition & 1 deletion schemas/v4.0.0/graphrandomerrorplugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
}
},
"rate": {
"type": "number",
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "The percentage (0-100) of requests that should be failed with a random error."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"number",
"boolean"
],
"description": "The body of the custom response returned when the token limit is exceeded."
"description": "The body of the custom response returned when the token limit is exceeded. Can be an object, array, string, number, or boolean."
},
"statusCode": {
"type": "integer",
Expand Down
10 changes: 8 additions & 2 deletions schemas/v4.0.0/mockrequestplugin.mockfile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@
"description": "HTTP method to use (default: POST)."
},
"body": {
"type": ["object", "array", "string", "number", "boolean"],
"description": "Body of the request."
"type": [
"object",
"array",
"string",
"number",
"boolean"
],
"description": "Body of the request (object, array, string, number, or boolean)."
},
"headers": {
"type": "array",
Expand Down
2 changes: 1 addition & 1 deletion schemas/v4.0.0/mockresponseplugin.mocksfile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"number",
"boolean"
],
"description": "The response body; string values can reference a file with '@filename'."
"description": "The response body (object, array, string, number, or boolean; strings can reference a file with '@filename')."
},
"statusCode": {
"type": "integer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"number",
"boolean"
],
"description": "The stdout content to return. If a string value starts with @, it's treated as a file path."
"description": "The stdout content to return. Can be an object, array, string, number, or boolean. If a string value starts with @, it's treated as a file path."
},
"stderr": {
"type": [
Expand All @@ -54,7 +54,7 @@
"number",
"boolean"
],
"description": "The stderr content to return. If a string value starts with @, it's treated as a file path."
"description": "The stderr content to return. Can be an object, array, string, number, or boolean. If a string value starts with @, it's treated as a file path."
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion schemas/v4.0.0/openapispecgeneratorplugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"v3_1",
"v3_2"
],
"description": "Specifies the OpenAPI spec version to generate. Allowed values: 'v2_0', 'v3_0', 'v3_1', or 'v3_2'. Default: 'v3_0'."
"description": "Specifies the OpenAPI spec version to generate. Allowed values: 'v2_0', 'v3_0', 'v3_1' or 'v3_2'. Default: 'v3_0'."
},
"specFormat": {
"type": "string",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"number",
"boolean"
],
"description": "The body of the custom response returned when the rate limit is exceeded."
"description": "The body of the custom response returned when the rate limit is exceeded. Can be an object, array, string, number, or boolean."
},
"statusCode": {
"type": "integer",
Expand Down
Loading
Loading