PAGI
view release on metacpan or search on metacpan
lib/PAGI/Middleware/ReverseProxy.pm view on Meta::CPAN
}
} elsif ($real_ip) {
if (exists $scope->{client}) {
$new_scope{client} = [$real_ip, $scope->{client}[1]];
$new_scope{original_client} = $scope->{client};
} else {
$new_scope{client} = [$real_ip, undef];
}
}
# X-Forwarded-Proto
my $forwarded_proto = $self->_get_header($scope, 'x-forwarded-proto');
if ($forwarded_proto) {
$forwarded_proto = lc($forwarded_proto);
$new_scope{scheme} = $forwarded_proto if $forwarded_proto =~ /^https?$/;
}
# X-Forwarded-Host
my $forwarded_host = $self->_get_header($scope, 'x-forwarded-host');
if ($forwarded_host) {
# Update headers with new Host
my @new_headers;
for my $h (@{$scope->{headers} // []}) {
if (lc($h->[0]) eq 'host') {
push @new_headers, ['host', $forwarded_host];
} else {
push @new_headers, $h;
}
}
$new_scope{headers} = \@new_headers;
}
# X-Forwarded-Port
my $forwarded_port = $self->_get_header($scope, 'x-forwarded-port');
if ($forwarded_port && $forwarded_port =~ /^\d+$/) {
$new_scope{server} = [$scope->{server}[0], int($forwarded_port)];
}
await $app->(\%new_scope, $receive, $send);
};
}
sub _is_trusted {
my ($self, $ip) = @_;
for my $trusted (@{$self->{trusted_proxies}}) {
if ($trusted =~ m{/}) {
# CIDR notation
return 1 if $self->_ip_in_cidr($ip, $trusted);
} else {
# Exact match
return 1 if $ip eq $trusted;
}
}
return 0;
}
sub _ip_in_cidr {
my ($self, $ip, $cidr) = @_;
my ($network, $bits) = split m{/}, $cidr;
# Simple IPv4 check
return 0 unless $ip =~ /^[\d.]+$/ && $network =~ /^[\d.]+$/;
my $ip_num = $self->_ip_to_num($ip);
my $net_num = $self->_ip_to_num($network);
return 0 unless defined $ip_num && defined $net_num;
my $mask = ~((1 << (32 - $bits)) - 1) & 0xFFFFFFFF;
return ($ip_num & $mask) == ($net_num & $mask);
}
sub _ip_to_num {
my ($self, $ip) = @_;
my @octets = split /\./, $ip;
return unless @octets == 4;
return unless _all(sub { /^\d+$/ && $_ >= 0 && $_ <= 255 }, @octets);
return ($octets[0] << 24) + ($octets[1] << 16) + ($octets[2] << 8) + $octets[3];
}
sub _all {
my ($code, @list) = @_;
for (@list) {
return 0 unless $code->();
}
return 1;
}
sub _get_header {
my ($self, $scope, $name) = @_;
$name = lc($name);
for my $h (@{$scope->{headers} // []}) {
return $h->[1] if lc($h->[0]) eq $name;
}
return;
}
1;
__END__
=head1 SCOPE MODIFICATIONS
When headers are processed from a trusted proxy:
=over 4
=item * client - Updated to original client [IP, port]
=item * original_client - The proxy's [IP, port]
=item * scheme - Updated to 'https' if X-Forwarded-Proto indicates
=item * headers - Host header updated if X-Forwarded-Host present
=item * server - Port updated if X-Forwarded-Port present
( run in 0.822 second using v1.01-cache-2.11-cpan-71847e10f99 )