using System.Diagnostics; using System.Net; using QVPN.Core; public static class SubscriptionDownloadTests { private static readonly string ProviderUrl = "https://test.withblancvpn.online/s/" + new string('a', 32); public static async Task HeadersDoNotEndBodyDeadlineAsync() { using var caller = new CancellationTokenSource(); using var body = new StallingStream(); bool headersReturned = false; using var handler = new StubHandler(async (_, token) => { await Task.Delay(30, token); headersReturned = true; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(body) }; }); using var http = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan }; var watch = Stopwatch.StartNew(); try { try { await Subscription.FetchWithClientAsync(ProviderUrl, http, TimeSpan.FromMilliseconds(150), caller.Token) .WaitAsync(TimeSpan.FromSeconds(1)); throw new InvalidOperationException("A stalled subscription body unexpectedly completed."); } catch (OperationCanceledException) { Check(!caller.IsCancellationRequested, "The download required caller cancellation instead of its own body deadline."); } Check(headersReturned && body.ReadStarted.Task.IsCompleted, "The regression did not reach the streamed body after receiving headers."); Check(body.ReadCancelled && body.Disposed, "The timed-out response did not cancel its body read and dispose the transport."); Check(watch.Elapsed < TimeSpan.FromSeconds(1), "The complete request exceeded its body deadline."); } finally { caller.Cancel(); } } public static async Task CallerCancellationWinsBodyDeadlineAsync() { using var caller = new CancellationTokenSource(); using var body = new StallingStream(); using var handler = new StubHandler((_, _) => Task.FromResult( new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(body) })); using var http = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan }; var running = Subscription.FetchWithClientAsync(ProviderUrl, http, TimeSpan.FromSeconds(5), caller.Token); await body.ReadStarted.Task.WaitAsync(TimeSpan.FromSeconds(1)); caller.Cancel(); try { await running.WaitAsync(TimeSpan.FromSeconds(1)); throw new InvalidOperationException("Caller cancellation was converted into a successful subscription."); } catch (OperationCanceledException error) { Check(error.CancellationToken == caller.Token, "The shorter caller deadline lost its original cancellation token."); } Check(body.ReadCancelled && body.Disposed, "Caller cancellation did not stop and dispose the streamed response."); } public static async Task CompletedBodyPreservesValidationAsync() { int requests = 0; using var handler = new StubHandler((request, _) => { Interlocked.Increment(ref requests); Check(request.RequestUri!.Scheme == Uri.UriSchemeHttps && request.RequestUri.Host == "test.withblancvpn.online", "The bounded download changed the validated HTTPS request."); string uuid = new Guid(Enumerable.Range(1, 16).Select(i => (byte)i).ToArray()).ToString(); string key = Convert.ToBase64String(Enumerable.Range(1, 32).Select(i => (byte)i).ToArray()).TrimEnd('=').Replace('+', '-').Replace('/', '_'); var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent($"vless://{uuid}@8.8.8.8:443?security=reality&type=tcp&pbk={key}&sid=abcd&sni=example.com&fp=chrome&flow=xtls-rprx-vision-udp443#Synthetic") }; response.Headers.TryAddWithoutValidation("subscription-userinfo", "upload=0; download=1"); return Task.FromResult(response); }); using var http = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan }; var result = await Subscription.FetchWithClientAsync(ProviderUrl, http, TimeSpan.FromSeconds(1), CancellationToken.None); Check(result.Nodes.Count == 1 && result.Nodes[0].Flow == "xtls-rprx-vision-udp443" && result.Info == "upload=0; download=1", "A complete bounded response lost nodes, supported transport fields, or subscription metadata."); try { await Subscription.FetchWithClientAsync("https://example.org/s/" + new string('a', 32), http, TimeSpan.FromSeconds(1), CancellationToken.None); throw new InvalidOperationException("Injected transport bypassed provider validation."); } catch (ArgumentException) { } Check(requests == 1, "An untrusted provider URL reached the injected transport."); } private static void Check(bool condition, string message) { if (!condition) throw new InvalidOperationException(message); } private sealed class StubHandler(Func> send) : HttpMessageHandler { protected override Task SendAsync(HttpRequestMessage request, CancellationToken token) => send(request, token); } private sealed class StallingStream : Stream { public TaskCompletionSource ReadStarted { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); public bool ReadCancelled { get; private set; } public bool Disposed { get; private set; } public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override async ValueTask ReadAsync(Memory buffer, CancellationToken token = default) { ReadStarted.TrySetResult(); try { await Task.Delay(Timeout.InfiniteTimeSpan, token); } catch (OperationCanceledException) { ReadCancelled = true; throw; } return 0; } protected override void Dispose(bool disposing) { Disposed = true; base.Dispose(disposing); } public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); public override void Flush() => throw new NotSupportedException(); public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } }