NAME

Langertha::HTTP::Redirect - The redirect policy both HTTP backends follow: credentials stay on their origin

VERSION

version 0.505

SYNOPSIS

use Langertha::HTTP::Redirect;

# Net::Async::HTTP, one hop at a time (max_redirects => 0):
my $next = Langertha::HTTP::Redirect::next_request( $request, $response );
return $response unless $next;              # not followed: the caller sees the 3xx

# LWP (Langertha::HTTP::UserAgent::redirect_ok), on the referral LWP built:
return 0 unless Langertha::HTTP::Redirect::guard_referral( $referral, $response );

DESCRIPTION

Internal module. Its interface may change without notice.

An engine puts its credential on each request for its own origin: an Authorization or x-api-key header, a ?key= query parameter. Neither HTTP client keeps it there on a redirect. LWP::UserAgent clones the request with every header (it drops only Authorization, and only since 6.83), so an x-api-key reached whatever host the server named; Net::Async::HTTP keeps the Location's query, so a server echoing the request URI (return 301 https://new.example$request_uri) sent Gemini's ?key= along (karr k374).

This module is the one policy both backends follow (Langertha::HTTP::UserAgent for LWP, Langertha::Role::AsyncHTTP for Net::Async::HTTP):

  • Only GET and HEAD are followed, on 301, 302, 303, 307 and 308 with a Location — what both clients follow by default. A POST (chat, embeddings, a key in the body) is never re-sent anywhere, even through an agent whose requests_redirectable lists it.

  • Only to http or https, and never from https down to http.

  • On the same origin (scheme, host and port) the request goes on unchanged, credential included. Cookie and Host are never carried over. http://host to https://host is another origin (the scheme differs): the credential is dropped, so a keyed GET behind an http-to-https redirect gets a 401 from the https side. Configure the engine with the https URL.

  • To another origin only the representation headers go along (Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Content-Language, User-Agent) — every other header is dropped, so an auth header of any name stays behind without a list of names to keep up to date. Userinfo is dropped from the new URL. Every query parameter whose value is a credential the chain of requests carried (a query value under a credential-like name such as key, or a credential-like header's value, without its Bearer/Basic scheme) is removed; the target's own query is otherwise kept byte for byte. If such a credential would still be in the new URL (in its path, say), the redirect is not followed.

A redirect that is not followed leaves the 3xx response to the caller, which then fails with its status line like any other non-success response. When this policy refused it, the response carries a Client-Warning header saying why (redirect not followed: Langertha::HTTP::Redirect: ...); so does the last 3xx when the hop limit ran out on Net::Async::HTTP.

same_origin

Langertha::HTTP::Redirect::same_origin( $uri_a, $uri_b );   # 1 or 0

True when both URLs have the same scheme, host and port (compared case-insensitively, a default port equal to its explicit number).

next_request

my $next = Langertha::HTTP::Redirect::next_request( $request, $response );
my $next = Langertha::HTTP::Redirect::next_request( $request, $response, $pinned_host );

The request to send for the redirect $response to $request, or undef when it is not followed. $request is not modified. The chain of earlier responses ($response->previous) is searched for credentials too. $pinned_host is passed on to "guard_referral".

guard_referral

my $follow = Langertha::HTTP::Redirect::guard_referral( $referral, $response );
my $follow = Langertha::HTTP::Redirect::guard_referral( $referral, $response, $pinned_host );

Applies the policy to $referral, the request about to be sent for the redirect $response (whose request is the hop it answers). Returns false when the redirect must not be followed, and then adds a Client-Warning header naming the reason to $response (redirect not followed: Langertha::HTTP::Redirect: ...); otherwise strips $referral in place if it goes to another origin and returns true. The method checked is the one of $response->request, so a POST is refused even when an agent's requests_redirectable would allow it.

When $pinned_host is given (the host of an engine's "connect_address" in Langertha::Role::HTTP), a redirect from that host to any other host is refused too (connect_address pins ...; not to another host): the pinned address was checked for that host only. A redirect staying on the host, on any port, is decided as usual and connects to the pinned address again.

SUPPORT

Issues

Please report bugs and feature requests at https://github.com/Getty/langertha/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 <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.

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