using System.Net;
using System.Net.Http;
using ClawdDotNet.Tools.WebFetch;
using Shouldly;
namespace ClawdDotNet.Tools.Tests.WebFetch;
///
/// #580: Checking the URL only sees the name. localtest.me is on no blocklist
/// and resolves to 127.0.0.1 - in the test run WebFetch thus opened a connection
/// to loopback. That's why the resolved address is checked, without a real network.
///
public sealed class UrlGuardResolutionTests
{
private static Func> Resolution(params string[] addresses)
=> (_, _) => Task.FromResult(addresses.Select(IPAddress.Parse).ToArray());
[Theory]
[InlineData("127.0.0.1")]
[InlineData("192.168.40.48")]
[InlineData("169.254.169.254")]
[InlineData("::1")]
[InlineData("::ffff:10.0.0.5")]
public async Task A_name_resolving_into_the_local_network_is_blocked(string address)
{
var ex = await Should.ThrowAsync(
() => UrlGuard.ResolveAllowedAsync("localtest.me", Resolution(address), default));
ex.Message.ShouldContain("localtest.me");
ex.Message.ShouldContain("blocked");
}
[Fact]
public async Task A_name_with_a_public_and_an_internal_address_is_blocked()
{
// Exactly the pattern with which you outwit a sampling check.
await Should.ThrowAsync(
() => UrlGuard.ResolveAllowedAsync("gemischt.example", Resolution("93.184.215.14", "127.0.0.1"), default));
}
[Fact]
public async Task A_name_with_public_addresses_is_allowed()
{
var addresses = await UrlGuard.ResolveAllowedAsync(
"example.com", Resolution("93.184.215.14", "2606:2800:21f:cb07:6820:80da:af6b:8b2c"), default);
addresses.Length.ShouldBe(2);
}
[Fact]
public async Task An_IP_literal_is_checked_without_resolution()
{
var called = false;
await Should.ThrowAsync(() => UrlGuard.ResolveAllowedAsync(
"[::1]",
(_, _) => { called = true; return Task.FromResult(Array.Empty()); },
default));
called.ShouldBeFalse();
}
[Fact]
public async Task A_name_without_address_is_not_connected()
=> await Should.ThrowAsync(
() => UrlGuard.ResolveAllowedAsync("nirgends.example", Resolution(), default));
}