Compare commits
2 Commits
39bead9da1
...
72013a9a08
| Author | SHA1 | Date | |
|---|---|---|---|
| 72013a9a08 | |||
| e6fc9c919f |
@@ -4,6 +4,7 @@ use Mojo::Base -base;
|
|||||||
use Mojo::URL;
|
use Mojo::URL;
|
||||||
use Mojo::UserAgent;
|
use Mojo::UserAgent;
|
||||||
use Mojo::Promise;
|
use Mojo::Promise;
|
||||||
|
use Mojo::IOLoop;
|
||||||
use Urupam::Utils qw(sanitize_url);
|
use Urupam::Utils qw(sanitize_url);
|
||||||
use Socket
|
use Socket
|
||||||
qw(getaddrinfo getnameinfo NI_NUMERICHOST NI_NUMERICSERV AF_INET AF_INET6 SOCK_STREAM);
|
qw(getaddrinfo getnameinfo NI_NUMERICHOST NI_NUMERICSERV AF_INET AF_INET6 SOCK_STREAM);
|
||||||
@@ -25,6 +26,9 @@ my @BLOCKED_DOMAINS = qw(
|
|||||||
localhost 127.0.0.1 0.0.0.0 ::1
|
localhost 127.0.0.1 0.0.0.0 ::1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
my $DNS_CACHE_TTL = 300;
|
||||||
|
my %dns_cache;
|
||||||
|
|
||||||
has ua => sub {
|
has ua => sub {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
Mojo::UserAgent->new(
|
Mojo::UserAgent->new(
|
||||||
@@ -146,25 +150,64 @@ sub _resolve_host {
|
|||||||
[ { type => 'ipv6', ip => $ipv6_host } ] );
|
[ { type => 'ipv6', ip => $ipv6_host } ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
my ( $err, @results ) =
|
my $cache_key = lc($host);
|
||||||
getaddrinfo( $host, undef, { socktype => SOCK_STREAM } );
|
my $now = time();
|
||||||
return Mojo::Promise->resolve( [] ) if $err;
|
if ( exists $dns_cache{$cache_key} ) {
|
||||||
|
my $cached = $dns_cache{$cache_key};
|
||||||
my @addresses;
|
if ( $now < $cached->{expires} ) {
|
||||||
for my $res (@results) {
|
return Mojo::Promise->resolve( $cached->{addresses} );
|
||||||
my ( $hostnum, undef ) =
|
|
||||||
getnameinfo( $res->{addr}, NI_NUMERICHOST | NI_NUMERICSERV );
|
|
||||||
next unless defined $hostnum && length $hostnum;
|
|
||||||
|
|
||||||
if ( $res->{family} == AF_INET ) {
|
|
||||||
push @addresses, { type => 'ipv4', ip => $hostnum };
|
|
||||||
}
|
|
||||||
elsif ( $res->{family} == AF_INET6 ) {
|
|
||||||
push @addresses, { type => 'ipv6', ip => $hostnum };
|
|
||||||
}
|
}
|
||||||
|
delete $dns_cache{$cache_key};
|
||||||
}
|
}
|
||||||
|
|
||||||
return Mojo::Promise->resolve( \@addresses );
|
my $promise = Mojo::Promise->new;
|
||||||
|
Mojo::IOLoop->subprocess(
|
||||||
|
sub {
|
||||||
|
my ($hostname) = @_;
|
||||||
|
my ( $err, @results ) =
|
||||||
|
getaddrinfo( $hostname, undef, { socktype => SOCK_STREAM } );
|
||||||
|
return { error => $err, results => \@results };
|
||||||
|
},
|
||||||
|
sub {
|
||||||
|
my ( $subprocess, $err, $data ) = @_;
|
||||||
|
if ($err) {
|
||||||
|
$promise->resolve( [] );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $res = $data;
|
||||||
|
if ( $res->{error} ) {
|
||||||
|
$promise->resolve( [] );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @addresses;
|
||||||
|
for my $result ( @{ $res->{results} } ) {
|
||||||
|
my ( $hostnum, undef ) =
|
||||||
|
getnameinfo( $result->{addr},
|
||||||
|
NI_NUMERICHOST | NI_NUMERICSERV );
|
||||||
|
next unless defined $hostnum && length $hostnum;
|
||||||
|
|
||||||
|
if ( $result->{family} == AF_INET ) {
|
||||||
|
push @addresses, { type => 'ipv4', ip => $hostnum };
|
||||||
|
}
|
||||||
|
elsif ( $result->{family} == AF_INET6 ) {
|
||||||
|
push @addresses, { type => 'ipv6', ip => $hostnum };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $addresses_ref = \@addresses;
|
||||||
|
$dns_cache{$cache_key} = {
|
||||||
|
addresses => $addresses_ref,
|
||||||
|
expires => $now + $DNS_CACHE_TTL
|
||||||
|
};
|
||||||
|
|
||||||
|
$promise->resolve($addresses_ref);
|
||||||
|
},
|
||||||
|
$host
|
||||||
|
);
|
||||||
|
|
||||||
|
return $promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub is_blocked_url {
|
sub is_blocked_url {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use Test::More;
|
|||||||
use Test::MockObject;
|
use Test::MockObject;
|
||||||
use Mojo::Promise;
|
use Mojo::Promise;
|
||||||
use Urupam::Validation;
|
use Urupam::Validation;
|
||||||
|
use Socket qw(AF_INET);
|
||||||
|
|
||||||
use_ok('Urupam::Validation');
|
use_ok('Urupam::Validation');
|
||||||
|
|
||||||
@@ -80,6 +81,18 @@ sub with_ssrf_ua {
|
|||||||
return $code->();
|
return $code->();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub with_subprocess_stub {
|
||||||
|
my ( $result, $code, $calls_ref ) = @_;
|
||||||
|
no warnings 'redefine';
|
||||||
|
local *Mojo::IOLoop::subprocess = sub {
|
||||||
|
my ( $class, $work, $finish, $host ) = @_;
|
||||||
|
$$calls_ref++ if defined $calls_ref;
|
||||||
|
$finish->( undef, undef, $result );
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
return $code->();
|
||||||
|
}
|
||||||
|
|
||||||
subtest 'is_valid_url_length' => sub {
|
subtest 'is_valid_url_length' => sub {
|
||||||
ok( $validator->is_valid_url_length('http://example.com'),
|
ok( $validator->is_valid_url_length('http://example.com'),
|
||||||
'valid URL length passes' );
|
'valid URL length passes' );
|
||||||
@@ -271,6 +284,38 @@ subtest 'is_blocked_url' => sub {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
subtest '_resolve_host - caches results' => sub {
|
||||||
|
my $calls = 0;
|
||||||
|
my $result = {
|
||||||
|
error => 0,
|
||||||
|
results => [ { addr => '127.0.0.1', family => AF_INET } ],
|
||||||
|
};
|
||||||
|
|
||||||
|
with_subprocess_stub(
|
||||||
|
$result,
|
||||||
|
sub {
|
||||||
|
my ( $value, $error ) =
|
||||||
|
wait_promise( $validator->_resolve_host('example.com') );
|
||||||
|
is( $error, undef, 'first resolve has no error' );
|
||||||
|
is( scalar @$value, 1, 'first resolve returns one address' );
|
||||||
|
},
|
||||||
|
\$calls
|
||||||
|
);
|
||||||
|
|
||||||
|
with_subprocess_stub(
|
||||||
|
$result,
|
||||||
|
sub {
|
||||||
|
my ( $value, $error ) =
|
||||||
|
wait_promise( $validator->_resolve_host('example.com') );
|
||||||
|
is( $error, undef, 'cached resolve has no error' );
|
||||||
|
is( scalar @$value, 1, 'cached resolve returns one address' );
|
||||||
|
},
|
||||||
|
\$calls
|
||||||
|
);
|
||||||
|
|
||||||
|
is( $calls, 1, 'subprocess called once due to cache' );
|
||||||
|
};
|
||||||
|
|
||||||
subtest 'validate_short_code' => sub {
|
subtest 'validate_short_code' => sub {
|
||||||
my @valid = (
|
my @valid = (
|
||||||
[ 'abc123456789', 'alphanumeric code passes' ],
|
[ 'abc123456789', 'alphanumeric code passes' ],
|
||||||
|
|||||||
Reference in New Issue
Block a user