NAME

Langertha::Raider::Provider::Fetch - Internal bounded fetch of a provider manifest from /.well-known/langertha.json

VERSION

version 0.503

SYNOPSIS

# Internal to Langertha-Raider -- no API promise.
my $fetch = Langertha::Raider::Provider::Fetch->new( allow_internal => 0 );
my $url   = $fetch->target_url('provider.example');   # croaks on a bad target
my $got   = $fetch->fetch_f($url)->get;
# { status => 'completed', url => ..., address => ..., body => ..., redirects => [...] }
# { status => 'refused' | 'failed', url => ..., error => ..., ... }

DESCRIPTION

Internal module. Its interface may change without notice.

Fetches a provider manifest (ADR 0007) the way a document from the network has to be fetched before anything trusts it:

  • https only, from /.well-known/langertha.json of the origin the target names. A target with userinfo, a query or a fragment is refused before anything is sent.

  • The target address is checked, and the connection goes to the checked address. The host is resolved once, every address it resolves to is classified ("address_kind"), and the request is sent to one of those addresses, never to a name that could resolve differently a second time. TLS still verifies the certificate against the host name (SNI and hostname check use the name, not the address).

  • Internal addresses are refused -- loopback, private (RFC 1918, shared address space, IPv6 unique local), link-local and reserved ranges -- unless "allow_internal" is set for a deliberately released internal Knarr or Skeid origin. Cloud metadata addresses, the unspecified address, multicast and the 240/4 range are refused always: none of them is ever a provider. A host whose addresses include one refused address is refused as a whole.

  • Limits: "max_bytes" of body (a larger Content-Length stops the fetch at the header, a longer body while it arrives), "timeout" seconds for everything from resolving to the last byte, "max_redirects" redirects. No content encoding is asked for, so nothing is decompressed.

  • A redirect is followed only within the origin (same scheme, host and port). A redirect to another origin is not followed and reported with its target, which is how a credential can never reach another origin through a redirect -- and this fetch sends none anyway: no Authorization, no cookies, no API key, only Accept and a User-Agent.

"fetch_f" never fails for a network or policy reason; it resolves to a report whose status says how the fetch ended. Validation of the body is the caller's job (Langertha::Manifest).

allow_internal

Release loopback, private, link-local and reserved addresses (see "address_kind"). Default false. Never releases a never address.

max_bytes

Largest body accepted, in bytes. Default 1 MiB.

timeout

Seconds the whole fetch may take, resolving included. Default 10.

max_redirects

How many same-origin redirects are followed. Default 3.

loop

The IO::Async::Loop. Defaults to IO::Async::Loop->new.

resolver

Code reference taking a host name and returning a Future of its addresses as strings. Defaults to the loop's resolver (getaddrinfo). An address literal is never handed to it.

ssl_options

Extra SSL_* arguments for the TLS connection, such as SSL_ca_file. Default none: the system's CA store. Cannot switch off the certificate or hostname check -- those are set after it.

target_url

my $url = $fetch->target_url('provider.example');        # https://provider.example/.well-known/langertha.json
my $url = $fetch->target_url('knarr.internal:8443');
my $url = $fetch->target_url('https://provider.example/');

The manifest URL a command-line target names: HOST, HOST:PORT, [IPv6]:PORT or an https URL whose path is empty, / or the well-known path itself. Croaks with the reason on anything else: another scheme (http included), userinfo, a query, a fragment or another path.

address_kind

my ( $kind, $releasable ) = $fetch->address_kind('10.0.0.5');   # ( 'private', 1 )
my ( $kind ) = $fetch->address_kind('93.184.216.34');           # ( 'public' )

Classifies an IPv4 or IPv6 address (a %scope suffix is ignored): public, or one of loopback, private, link-local, reserved (releasable with "allow_internal") or metadata, unspecified, multicast, invalid (never). IPv4-mapped, IPv4-compatible, NAT64 (64:ff9b::/96) and 6to4 (2002::/16) IPv6 addresses are classified by the IPv4 address they carry. The second value is true for a releasable kind.

check_addresses

my $refusal = $fetch->check_addresses($host, @addresses);

undef when every address may be connected to, else the reason the first one that may not is refused.

fetch_f

my $report = await $fetch->fetch_f($url);

Fetches the manifest at $url (from "target_url") and resolves to a report hash:

status -- completed, refused (an address or a redirect the policy does not allow) or failed (network, TLS, HTTP status, a limit).
url -- the URL asked for; final_url -- the one the body came from (after same-origin redirects).
address -- the address connected to (once there is one).
redirects -- the same-origin locations followed, in order.
body -- the body octets (completed only).
error -- why (refused and failed); location -- the target of a redirect that was not followed.

check_host_f

my $checked = await $fetch->check_host_f('provider.example');
# { addresses => [ '93.184.216.34' ] }
# { status => 'refused' | 'failed', error => ... }

Resolves $host (an address literal stands for itself) and checks every address with "check_addresses". Resolves to the addresses, or to the refused or failed status and why.

origin

my $origin = $fetch->origin('https://Provider.example/v1');   # https://provider.example:443

Scheme, host and port of a URL (string or URI), lower-cased and with the default port spelled out, so two origins compare with eq; undef for a URL without a host.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-raider/issues.

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.