feat: add check ssl and check url reachability
This commit is contained in:
@@ -2,33 +2,125 @@ package Urupam::Validation;
|
|||||||
|
|
||||||
use Mojo::Base -base;
|
use Mojo::Base -base;
|
||||||
use Mojo::URL;
|
use Mojo::URL;
|
||||||
|
use Mojo::UserAgent;
|
||||||
use Mojo::Util qw(url_unescape);
|
use Mojo::Util qw(url_unescape);
|
||||||
|
use Mojo::Promise;
|
||||||
|
|
||||||
|
my $MAX_URL_LENGTH = 2048;
|
||||||
|
|
||||||
|
my @BLOCKED_DOMAINS = qw(
|
||||||
|
localhost 127.0.0.1 0.0.0.0 ::1
|
||||||
|
);
|
||||||
|
|
||||||
|
has ua => sub {
|
||||||
|
Mojo::UserAgent->new(
|
||||||
|
connect_timeout => 10,
|
||||||
|
request_timeout => 10,
|
||||||
|
max_redirects => 3,
|
||||||
|
insecure => 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
sub _parse_url {
|
||||||
|
my ( $self, $url ) = @_;
|
||||||
|
return eval { Mojo::URL->new($url) };
|
||||||
|
}
|
||||||
|
|
||||||
sub is_valid_url {
|
sub is_valid_url {
|
||||||
my ( $self, $url ) = @_;
|
my ( $self, $url ) = @_;
|
||||||
return 0 unless defined $url && length($url) > 0;
|
return 0 unless defined $url && length($url) > 0;
|
||||||
|
|
||||||
$url = url_unescape($url) if $url =~ /%[0-9A-Fa-f]{2}/;
|
my $parsed = $self->_parse_url($url);
|
||||||
|
|
||||||
return 0 unless $url =~ m{^https?://}i;
|
|
||||||
|
|
||||||
my $parsed = eval { Mojo::URL->new($url) };
|
|
||||||
return 0 unless $parsed;
|
return 0 unless $parsed;
|
||||||
|
return 0 unless $parsed->scheme && $parsed->scheme =~ /^https?$/i;
|
||||||
return 0 unless $parsed->scheme && $parsed->host;
|
return 0 unless $parsed->host;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub is_valid_url_length {
|
||||||
|
my ( $self, $url ) = @_;
|
||||||
|
return defined $url && length($url) <= $MAX_URL_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub is_blocked_url {
|
||||||
|
my ( $self, $url ) = @_;
|
||||||
|
return 0 unless defined $url;
|
||||||
|
|
||||||
|
my $parsed = $self->_parse_url($url);
|
||||||
|
return 0 unless $parsed;
|
||||||
|
|
||||||
|
my $host = lc( $parsed->host // '' );
|
||||||
|
$host =~ s/:.*$//;
|
||||||
|
|
||||||
|
for my $blocked (@BLOCKED_DOMAINS) {
|
||||||
|
return 1 if $host eq $blocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
if $host =~ /^(127\.|192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)/;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_url_reachable {
|
||||||
|
my ( $self, $url ) = @_;
|
||||||
|
|
||||||
|
return Mojo::Promise->reject('URL is required')
|
||||||
|
unless defined $url && length($url) > 0;
|
||||||
|
|
||||||
|
return $self->ua->head_p($url)->then(
|
||||||
|
sub {
|
||||||
|
my $tx = shift;
|
||||||
|
my $code = $tx->result->code;
|
||||||
|
|
||||||
|
return 1 if $code >= 200 && $code < 400;
|
||||||
|
return Mojo::Promise->reject("URL returned $code error")
|
||||||
|
if $code >= 400;
|
||||||
|
return Mojo::Promise->reject(
|
||||||
|
"URL returned unexpected status: $code");
|
||||||
|
}
|
||||||
|
)->catch(
|
||||||
|
sub {
|
||||||
|
my $err = shift;
|
||||||
|
my $err_str = "$err";
|
||||||
|
if ( $err_str =~ /SSL|certificate|TLS/i ) {
|
||||||
|
return Mojo::Promise->reject("SSL certificate error: $err_str");
|
||||||
|
}
|
||||||
|
if ( $err_str =~ /Connection refused|Can't connect|timeout/i ) {
|
||||||
|
return Mojo::Promise->reject("Cannot reach URL: $err_str");
|
||||||
|
}
|
||||||
|
return Mojo::Promise->reject("URL validation failed: $err_str");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_ssl_certificate {
|
||||||
|
my ( $self, $url ) = @_;
|
||||||
|
|
||||||
|
return Mojo::Promise->reject('URL is required')
|
||||||
|
unless defined $url && length($url) > 0;
|
||||||
|
|
||||||
|
my $parsed = $self->_parse_url($url);
|
||||||
|
return Mojo::Promise->resolve(1)
|
||||||
|
unless $parsed && $parsed->scheme && $parsed->scheme eq 'https';
|
||||||
|
|
||||||
|
return $self->ua->head_p($url)->then( sub { return 1; } )->catch(
|
||||||
|
sub {
|
||||||
|
my $err = shift;
|
||||||
|
my $err_str = "$err";
|
||||||
|
if ( $err_str =~ /SSL|certificate|TLS|verification failed/i ) {
|
||||||
|
return Mojo::Promise->reject(
|
||||||
|
"Invalid SSL certificate: $err_str");
|
||||||
|
}
|
||||||
|
return Mojo::Promise->reject("SSL check failed: $err_str");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
sub is_valid_short_code {
|
sub is_valid_short_code {
|
||||||
my ( $self, $code ) = @_;
|
my ( $self, $code ) = @_;
|
||||||
return 0 unless defined $code && length($code) > 0;
|
return defined $code && length($code) > 0 && $code =~ /^[0-9a-zA-Z]+$/;
|
||||||
|
|
||||||
return 0 if length($code) > 20;
|
|
||||||
|
|
||||||
return 0 unless $code =~ /^[0-9a-zA-Z]+$/;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub sanitize_url {
|
sub sanitize_url {
|
||||||
@@ -38,12 +130,38 @@ sub sanitize_url {
|
|||||||
$url =~ s/^\s+|\s+$//g;
|
$url =~ s/^\s+|\s+$//g;
|
||||||
$url = url_unescape($url) if $url =~ /%[0-9A-Fa-f]{2}/;
|
$url = url_unescape($url) if $url =~ /%[0-9A-Fa-f]{2}/;
|
||||||
|
|
||||||
unless ( $url =~ m{^https?://}i ) {
|
$url = 'http://' . $url unless $url =~ m{^https?://}i;
|
||||||
$url = 'http://' . $url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
sub validate_url_with_checks {
|
||||||
|
my ( $self, $url ) = @_;
|
||||||
|
|
||||||
|
return Mojo::Promise->reject('URL is required')
|
||||||
|
unless defined $url && length($url) > 0;
|
||||||
|
|
||||||
|
my $sanitized = $self->sanitize_url($url);
|
||||||
|
return Mojo::Promise->reject('Invalid URL format')
|
||||||
|
unless $self->is_valid_url($sanitized);
|
||||||
|
return Mojo::Promise->reject(
|
||||||
|
"URL exceeds maximum length of $MAX_URL_LENGTH characters")
|
||||||
|
unless $self->is_valid_url_length($sanitized);
|
||||||
|
return Mojo::Promise->reject(
|
||||||
|
'This URL cannot be shortened (blocked domain or local address)')
|
||||||
|
if $self->is_blocked_url($sanitized);
|
||||||
|
|
||||||
|
my $parsed = $self->_parse_url($sanitized);
|
||||||
|
return Mojo::Promise->reject('Invalid URL format') unless $parsed;
|
||||||
|
|
||||||
|
my $ssl_check =
|
||||||
|
$parsed->scheme eq 'https'
|
||||||
|
? $self->check_ssl_certificate($sanitized)
|
||||||
|
: Mojo::Promise->resolve(1);
|
||||||
|
|
||||||
|
return $ssl_check->then(
|
||||||
|
sub { return $self->check_url_reachable($sanitized); } )
|
||||||
|
->then( sub { return $sanitized; } );
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|||||||
Reference in New Issue
Block a user