use
vars
qw($UA $REGISTRY)
;
CACHE_TTL
=> 86400,
};
our
$UA
;
our
$REGISTRY
= {};
sub
get_url {
my
(
$package
,
$object
) =
@_
;
if
(
'Net::IP'
eq
ref
(
$object
)) {
return
$package
->ip(
$object
);
}
elsif
(
'Net::ASN'
eq
ref
(
$object
)) {
return
$package
->autnum(
$object
);
}
elsif
(
'Net::DNS::Domain'
eq
ref
(
$object
)) {
return
$package
->domain(
$object
);
}
elsif
(
$object
=~ /-/) {
return
$package
->entity(
$object
);
}
else
{
croak(
"Unable to deal with '$object'"
);
}
}
sub
ip {
my
(
$package
,
$ip
) =
@_
;
croak(
sprintf
(
'Argument to %s->ip() must be a Net::IP'
,
$package
))
unless
(
'Net::IP'
eq
ref
(
$ip
));
my
$registry
=
$package
->load_registry(4 ==
$ip
->version ? IP4_URL : IP6_URL);
return
undef
if
(!
$registry
);
my
%matches
;
SERVICE:
foreach
my
$service
(
$registry
->services) {
VALUE:
foreach
my
$value
(
$service
->registries) {
my
$range
= Net::IP->new(
$value
);
if
(
$range
->overlaps(
$ip
)) {
$matches
{
$value
} =
$package
->get_best_url(
$service
->urls);
last
VALUE;
}
}
}
return
undef
if
(
scalar
(
keys
(
%matches
)) < 1);
my
$longest
= (
sort
{ Net::IP->new(
$b
)->prefixlen <=> Net::IP->new(
$a
)->prefixlen }
keys
(
%matches
))[0];
return
$package
->assemble_url(
$matches
{
$longest
},
'ip'
,
split
(/\//,
$ip
->prefix));
}
sub
autnum {
my
(
$package
,
$autnum
) =
@_
;
croak(
sprintf
(
'Argument to %s->autnum() must be a Net::ASN'
,
$package
))
unless
(
'Net::ASN'
eq
ref
(
$autnum
));
my
$registry
=
$package
->load_registry(ASN_URL);
return
undef
if
(!
$registry
);
my
%matches
;
SERVICE:
foreach
my
$service
(
$registry
->services) {
VALUE:
foreach
my
$value
(
$service
->registries) {
if
(
$value
=~ /^\d+$/ &&
$value
==
$autnum
->toasplain) {
$matches
{
sprintf
(
'%d-%d'
,
$value
,
$value
)} =
$package
->get_best_url(
$service
->urls);
last
SERVICE;
}
elsif
(
$value
=~ /^(\d+)-(\d+)$/) {
if
($1 <=
$autnum
->toasplain &&
$autnum
->toasplain <= $2) {
$matches
{
$value
} =
$package
->get_best_url(
$service
->urls);
last
VALUE;
}
}
}
}
return
undef
if
(
scalar
(
keys
(
%matches
)) < 1);
my
@ranges
=
keys
(
%matches
);
my
@pairs
=
map
{ [
split
(/-/,
$_
, 2) ] }
@ranges
;
my
@sorted
=
sort
{
$b
->{1} -
$b
->{0} <=>
$a
->{1} -
$a
->{0} }
@pairs
;
my
$closest
=
sprintf
(
'%d-%d'
, @{
$sorted
[0]});
return
$package
->assemble_url(
$matches
{
$closest
},
'autnum'
,
$autnum
->toasplain);
}
sub
domain {
my
(
$package
,
$domain
) =
@_
;
croak(
sprintf
(
'Argument to %s->domain() must be a Net::DNS::Domain'
,
$package
))
unless
(
'Net::DNS::Domain'
eq
ref
(
$domain
));
my
$is_tld
= (1 ==
scalar
(
$domain
->label));
my
$registry
=
$package
->load_registry(DNS_URL);
return
undef
if
(!
$registry
);
my
%matches
;
SERVICE:
foreach
my
$service
(
$registry
->services) {
VALUE:
foreach
my
$value
(
$service
->registries) {
if
(
$is_tld
&&
''
eq
$value
) {
$matches
{
$value
} =
$package
->get_best_url(
$service
->urls);
last
VALUE;
}
else
{
if
(
$domain
->name =~ /\.
$value
$/i) {
$matches
{
$value
} =
$package
->get_best_url(
$service
->urls);
last
VALUE;
}
}
}
}
if
(
scalar
(
keys
(
%matches
)) < 1) {
if
(
$domain
->name =~ /\.(in-addr|ip6)\.arpa$/) {
return
$package
->reverse_domain(
$domain
);
}
else
{
return
undef
;
}
}
else
{
my
$parent
= (
sort
{
length
(
$b
) <=>
length
(
$a
) }
keys
(
%matches
))[0];
return
$package
->assemble_url(
$matches
{
$parent
},
'domain'
,
$domain
->name);
}
}
sub
reverse_domain {
my
(
$package
,
$domain
) =
@_
;
my
@labels
=
reverse
(
$domain
->label);
shift
(
@labels
);
my
$ip
;
if
(
'ip6'
eq
shift
(
@labels
)) {
my
@parts
;
push
(
@parts
,
join
(
''
,
splice
(
@labels
, 0, 4)))
while
(
scalar
(
@labels
) > 0);
pop
(
@parts
)
while
(0 ==
hex
(
$parts
[-1]));
my
$prefixlen
= 16 * (
scalar
(
@parts
));
$ip
= Net::IP->new(
sprintf
(
'%s:%s:%s:%s:%s:%s:%s:%s/%u'
,
shift
(
@parts
) || 0,
shift
(
@parts
) || 0,
shift
(
@parts
) || 0,
shift
(
@parts
) || 0,
shift
(
@parts
) || 0,
shift
(
@parts
) || 0,
shift
(
@parts
) || 0,
shift
(
@parts
) || 0,
$prefixlen
,
));
}
else
{
pop
(
@labels
)
while
(0 ==
$labels
[-1]);
my
$prefixlen
= 8 * (
scalar
(
@labels
));
$ip
= Net::IP->new(
sprintf
(
'%u.%u.%u.%u/%u'
,
shift
(
@labels
) || 0,
shift
(
@labels
) || 0,
shift
(
@labels
) || 0,
shift
(
@labels
) || 0,
$prefixlen
,
));
}
return
undef
if
(!
$ip
);
my
$url
=
$package
->ip(
$ip
);
return
undef
if
(!
$url
);
return
URI->new_abs(
sprintf
(
'../../domain/%s'
,
$domain
->name),
$url
);
}
sub
entity {
my
(
$package
,
$handle
) =
@_
;
my
@parts
=
split
(/-/,
$handle
);
my
$tag
=
pop
(
@parts
);
my
$registry
=
$package
->load_registry(TAG_URL);
return
undef
if
(!
$registry
);
foreach
my
$service
(
$registry
->services) {
foreach
my
$value
(
$service
->registries) {
return
$package
->assemble_url(
$package
->get_best_url(
$service
->urls),
'entity'
,
$handle
)
if
(
lc
(
$value
) eq
lc
(
$tag
));
}
}
return
undef
;
}
sub
load_registry {
my
(
$package
,
$url
) =
@_
;
if
(!
defined
(
$REGISTRY
->{
$url
})) {
$package
=~ s/:+/-/g;
my
$file
=
sprintf
(
'%s/%s-%s-%s'
,
File::Spec->tmpdir,
$package
,
basename(
$url
),
getpwuid
($<),
);
$UA
= Net::RDAP::UA->new
unless
(
defined
(
$UA
));
$UA
->mirror(
$url
,
$file
, CACHE_TTL);
$REGISTRY
->{
$url
} = Net::RDAP::Registry::IANARegistry->new(from_json(read_file(
$file
)))
if
(-e
$file
);
}
return
$REGISTRY
->{
$url
};
}
sub
get_best_url {
my
(
$package
,
@urls
) =
@_
;
my
@https
=
grep
{
'https'
eq
lc
(
$_
->scheme) }
@urls
;
if
(
scalar
(
@https
)) {
return
shift
(
@https
);
}
else
{
return
shift
(
@urls
);
}
}
sub
assemble_url {
my
(
$package
,
$url
,
@segments
) =
@_
;
$url
->path_segments(
grep
{
defined
&&
length
> 0 } (
$url
->path_segments,
@segments
));
return
$url
;
}
1;