NAME

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

VERSION

version 0.503

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 on GitHub 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 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.