Skip to content

Commit 90b6087

Browse files
Fixes forwarding https URLs on mac (#1768)
1 parent 909c9a3 commit 90b6087

3 files changed

Lines changed: 103 additions & 14 deletions

File tree

‎DevProxy.Proxy.Kestrel/Internal/ProxyConnectionHandler.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ private async Task BlindTunnelAsync(Stream clientStream, string host, int port,
278278
private async Task<bool> ExchangeAsync(
279279
Http1ConnectionReader reader, Stream clientStream, ParsedRequestHead head, string absoluteUrl, CancellationToken ct)
280280
{
281-
if (!Uri.TryCreate(absoluteUrl, UriKind.Absolute, out var requestUri))
281+
if (!Uri.TryCreate(absoluteUrl, UriKind.Absolute, out var requestUri)
282+
|| (requestUri.Scheme != "http" && requestUri.Scheme != "https"))
282283
{
283284
await WriteErrorAsync(clientStream, HttpStatusCode.BadRequest, "Malformed request target", ct).ConfigureAwait(false);
284285
return false;

‎DevProxy/Commands/StopCommand.cs‎

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,16 @@ private async Task<int> RunAsync(ParseResult parseResult, CancellationToken canc
3636

3737
if (pid is not null)
3838
{
39-
var state = await StateManager.LoadStateByPidAsync(pid.Value, cancellationToken);
40-
if (state is null)
41-
{
42-
Console.WriteLine($"No running Dev Proxy instance with PID {pid.Value}.");
43-
return 1;
44-
}
45-
46-
return await StopInstanceAsync(state, force, cancellationToken);
39+
return await StopByPidAsync(pid.Value, force, cancellationToken);
4740
}
4841

42+
// Reconcile any system-proxy registrations left behind by crashed
43+
// instances first — this must run before LoadAllStatesAsync, which prunes
44+
// (deletes) the stale state files it depends on.
45+
var reconciliation = await SystemProxyManager.ReconcileOrphanedSystemProxiesAsync(cancellationToken);
46+
4947
var states = await StateManager.LoadAllStatesAsync(cancellationToken);
50-
if (states.Count == 0)
48+
if (states.Count == 0 && reconciliation.Orphans.Count == 0)
5149
{
5250
Console.WriteLine("Dev Proxy is not running.");
5351
return 1;
@@ -63,9 +61,55 @@ private async Task<int> RunAsync(ParseResult parseResult, CancellationToken canc
6361
}
6462
}
6563

64+
ReportReconciledOrphans(reconciliation);
65+
6666
return exitCode;
6767
}
6868

69+
private static async Task<int> StopByPidAsync(int pid, bool force, CancellationToken cancellationToken)
70+
{
71+
// Capture a potential orphaned system-proxy record before LoadStateByPidAsync
72+
// prunes (deletes) the stale state file for a dead PID.
73+
var orphan = (await StateManager.GetOrphanedSystemProxyStatesAsync(cancellationToken))
74+
.Find(o => o.Pid == pid);
75+
76+
var state = await StateManager.LoadStateByPidAsync(pid, cancellationToken);
77+
if (state is not null)
78+
{
79+
return await StopInstanceAsync(state, force, cancellationToken);
80+
}
81+
82+
if (orphan is not null)
83+
{
84+
var liveOwner = await StateManager.FindSystemProxyInstanceAsync(cancellationToken);
85+
if (liveOwner is null)
86+
{
87+
new SystemProxyManager(NullLogger<SystemProxyManager>.Instance).Disable();
88+
Console.WriteLine($"Restored system proxy left by crashed Dev Proxy (PID: {pid}).");
89+
}
90+
else
91+
{
92+
Console.WriteLine($"Removed stale record for crashed Dev Proxy (PID: {pid}); system proxy is owned by a running instance (PID: {liveOwner.Pid}).");
93+
}
94+
95+
await StateManager.DeleteStateAsync(pid, cancellationToken);
96+
return 0;
97+
}
98+
99+
Console.WriteLine($"No running Dev Proxy instance with PID {pid}.");
100+
return 1;
101+
}
102+
103+
private static void ReportReconciledOrphans(SystemProxyManager.OrphanReconciliation reconciliation)
104+
{
105+
foreach (var orphan in reconciliation.Orphans)
106+
{
107+
Console.WriteLine(reconciliation.SystemProxyDisabled
108+
? $"Restored system proxy left by crashed Dev Proxy (PID: {orphan.Pid})."
109+
: $"Removed stale record for crashed Dev Proxy (PID: {orphan.Pid}); system proxy is owned by a running instance.");
110+
}
111+
}
112+
69113
private static async Task<int> StopInstanceAsync(ProxyInstanceState state, bool force, CancellationToken cancellationToken)
70114
{
71115
if (force)
@@ -148,10 +192,12 @@ private static async Task<int> StopInstanceAsync(ProxyInstanceState state, bool
148192

149193
private static async Task<int> ForceStopAsync(ProxyInstanceState state, CancellationToken cancellationToken)
150194
{
151-
// Best-effort: restore the OS proxy in case the daemon is killed before it can
152-
// deregister itself (SIGKILL can't be caught; a crashed daemon never cleans up).
153-
// Engine-agnostic and cross-platform (Windows WinINET + macOS toggle-proxy.sh).
154-
new SystemProxyManager(NullLogger<SystemProxyManager>.Instance).Disable();
195+
// A killed process can't run its own cleanup, so restore the system proxy
196+
// on its behalf before terminating it.
197+
if (state.AsSystemProxy)
198+
{
199+
new SystemProxyManager(NullLogger<SystemProxyManager>.Instance).Disable();
200+
}
155201

156202
try
157203
{

‎DevProxy/Proxy/SystemProxyManager.cs‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.Runtime.InteropServices;
77
using System.Runtime.Versioning;
88
using DevProxy.Abstractions.Proxy;
9+
using DevProxy.State;
10+
using Microsoft.Extensions.Logging.Abstractions;
911
using Microsoft.Win32;
1012

1113
namespace DevProxy.Proxy;
@@ -160,4 +162,44 @@ private void RunToggleScript(string arguments)
160162
logger.LogError(ex, "Failed to toggle the system proxy via toggle-proxy.sh.");
161163
}
162164
}
165+
166+
// ------------------------------------------------------------------
167+
// Static helpers for orphan reconciliation (crash-cleanup path).
168+
// ------------------------------------------------------------------
169+
170+
/// <summary>
171+
/// The outcome of reconciling orphaned system-proxy registrations.
172+
/// </summary>
173+
internal readonly record struct OrphanReconciliation(
174+
IReadOnlyList<ProxyInstanceState> Orphans,
175+
bool SystemProxyDisabled);
176+
177+
/// <summary>
178+
/// Reconciles system-proxy registrations left behind by crashed instances.
179+
/// Restores the OS proxy (unless a live instance still owns it) and removes
180+
/// the stale state records. Safe to call when there are no orphans.
181+
/// </summary>
182+
public static async Task<OrphanReconciliation> ReconcileOrphanedSystemProxiesAsync(CancellationToken cancellationToken = default)
183+
{
184+
var orphans = await StateManager.GetOrphanedSystemProxyStatesAsync(cancellationToken);
185+
if (orphans.Count == 0)
186+
{
187+
return new([], false);
188+
}
189+
190+
var liveOwner = await StateManager.FindSystemProxyInstanceAsync(cancellationToken);
191+
var disabled = false;
192+
if (liveOwner is null)
193+
{
194+
new SystemProxyManager(NullLogger<SystemProxyManager>.Instance).Disable();
195+
disabled = true;
196+
}
197+
198+
foreach (var orphan in orphans)
199+
{
200+
await StateManager.DeleteStateAsync(orphan.Pid, cancellationToken);
201+
}
202+
203+
return new(orphans, disabled);
204+
}
163205
}

0 commit comments

Comments
 (0)