#!/usr/bin/perl
#
# Plack::Middleware::DW::XForwardedFor
#
# Sets up IP address based on proxy trust.
#
# Authors:
#      Mark Smith <mark@dreamwidth.org>
#
# Copyright (c) 2021 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#

package Plack::Middleware::DW::XForwardedFor;

use strict;
use v5.10;
use Log::Log4perl;
my $log = Log::Log4perl->get_logger(__PACKAGE__);

use parent qw/ Plack::Middleware /;

sub call {
    my ( $self, $env ) = @_;

    return $self->app->($env)
        unless $LJ::TRUST_X_HEADERS;

    my $r = DW::Request->get;

    # Assume user's IP comes in the real IP, but if not, we need to grab it out of
    # the X-Forwarded-For -- respecting IS_TRUSTED_PROXY
    if ( my $forward = $r->header_in('X-Forwarded-For') ) {
        my @hosts = split( /\s*,\s*/, $forward );
        if (@hosts) {
            my $real;
            if ( ref $LJ::IS_TRUSTED_PROXY eq 'CODE' ) {

                # Find last IP in X-Forwarded-For that isn't a trusted proxy.
                do {
                    $real = pop @hosts;
                } while ( @hosts && $LJ::IS_TRUSTED_PROXY->($real) );
            }
            else {
                # Trust everything by default, real client IP is first.
                $real  = shift @hosts;
                @hosts = ();
            }
            $r->address($real);

            # Also update the PSGI env so infrastructure that reads it
            # directly (Starman access log, Sysban, etc.) sees the real IP.
            $env->{REMOTE_ADDR} = $real;
        }
        $r->header_in( 'X-Forwarded-For' => join( ", ", @hosts ) );
    }

    # and now, deal with getting the right Host header
    my $host = $r->header_in('X-Host') // $r->header_in('X-Forwarded-Host');
    $r->header_in( 'Host' => $host ) if defined $host;

    return $self->app->($env);
}

1;
