File Coverage

blib/lib/App/rdapper.pm
Criterion Covered Total %
statement 238 406 58.6
branch 91 204 44.6
condition 32 81 39.5
subroutine 45 64 70.3
pod 1 38 2.6
total 407 793 51.3


line stmt bran cond sub pod time code
1             package App::rdapper;
2 2     2   1063051 use App::rdapper::l10n;
  2         11  
  2         155  
3 2     2   13 use Encode qw(decode);
  2         3  
  2         95  
4 2     2   1397 use File::ShareDir qw(:ALL);
  2         67460  
  2         494  
5 2     2   1863 use Getopt::Long qw(GetOptionsFromArray :config pass_through);
  2         26368  
  2         14  
6 2     2   2369 use JSON;
  2         22487  
  2         18  
7 2     2   346 use List::Util qw(any min max uniq);
  2         3  
  2         194  
8 2     2   1016 use Net::ASN;
  2         5972  
  2         112  
9 2     2   1035 use Net::DNS::Domain;
  2         11604  
  2         112  
10 2     2   1241 use Net::IDN::PP;
  2         3934  
  2         83  
11 2     2   1630 use Net::IP;
  2         200957  
  2         598  
12 2     2   1526 use Net::RDAP::EPPStatusMap;
  2         1418  
  2         230  
13 2     2   1376 use Net::RDAP 0.41;
  2         426554  
  2         109  
14 2     2   1767 use Pod::Usage;
  2         140006  
  2         515  
15 2     2   32 use POSIX qw(setlocale LC_ALL);
  2         5  
  2         30  
16 2     2   2306 use Term::ANSIColor;
  2         27066  
  2         382  
17 2     2   31 use Term::Size;
  2         6  
  2         127  
18 2     2   1751 use Text::Wrap;
  2         8264  
  2         211  
19 2     2   25 use URI;
  2         7  
  2         171  
20             use constant {
21 2         385 GETTEXT_DOMAIN => q{rdapper},
22             # see RFC 6350, Section 6.3.1.
23             'ADR_STREET' => 2,
24             'ADR_CITY' => 3,
25             'ADR_SP' => 4,
26             'ADR_PC' => 5,
27             'ADR_CC' => 6,
28             'INDENT' => ' ',
29             'IANA_BASE_URL' => 'https://rdap.iana.org/',
30 2     2   19 };
  2         5  
31 2     2   1589 use locale;
  2         2203  
  2         16  
32 2     2   139 use vars qw($VERSION $LH);
  2         21  
  2         184  
33 2     2   17 use strict;
  2         7  
  2         22020  
34              
35             $VERSION = '1.24';
36              
37             $LH = App::rdapper::l10n->get_handle;
38              
39             $LH->die_for_lookup_failures(1);
40             $LH->bindtextdomain(GETTEXT_DOMAIN, $ENV{RDAPPER_LOCALE_DIR} || module_dir(__PACKAGE__));
41             $LH->textdomain(GETTEXT_DOMAIN);
42              
43             #
44             # global arg variables
45             #
46             my (
47             $type, $object, $help, $short, $bypass, $auth, $nopager, $raw, $both,
48             $registrar, $nocolor, $reverse, $version, $search, $debug, $registry,
49             $strings
50             );
51              
52             #
53             # options spec for Getopt::Long
54             #
55             my %opts = (
56             'type:s' => \$type,
57             'object:s' => \$object,
58             'help' => \$help,
59             'short' => \$short,
60             'bypass-cache' => \$bypass,
61             'auth:s' => \$auth,
62             'nopager' => \$nopager, # ignored
63             'raw' => \$raw,
64             'both' => \$both,
65             'registrar' => \$registrar,
66             'registry' => \$registry,
67             'nocolor' => \$nocolor,
68             'reverse' => \$reverse,
69             'version' => \$version,
70             'search' => \$search,
71             'debug' => \$debug,
72             'autnum' => sub { $type = 'autnum' },
73             'domain' => sub { $type = 'domain' },
74             'nameserver' => sub { $type = 'nameserver' },
75             'entity' => sub { $type = 'entity' },
76             'ip' => sub { $type = 'ip' },
77             'tld' => sub { $type = 'tld' },
78             'url' => sub { $type = 'url' },
79             'strings' => \$strings,
80             );
81              
82             my $funcs = {
83             'ip network' => sub { __PACKAGE__->print_ip(@_) },
84             'autnum' => sub { __PACKAGE__->print_asn(@_) },
85             'domain' => sub { __PACKAGE__->print_domain(@_) },
86             'entity' => sub { __PACKAGE__->print_entity(@_) },
87             'nameserver' => sub { __PACKAGE__->print_nameserver(@_) },
88             'help' => sub { 1 }, # help only contains generic properties
89             };
90              
91             my $OBJECT_TYPE_DISPLAY_NAMES = {
92             'ip network' => _('IP Network'),
93             'autnum' => _('Autonomous System'),
94             'domain' => _('Domain Name'),
95             'entity' => _('Entity'),
96             'nameserver' => _('Nameserver'),
97             'help' => _('Help'),
98             };
99              
100             my @ROLE_DISPLAY_NAMES_ORDER = qw(registrant administrative technical billing
101             abuse registrar reseller sponsor proxy notifications noc);
102              
103             my %ROLE_DISPLAY_NAMES = (
104             'registrant' => _('Registrant'),
105             'technical' => _('Technical'),
106             'administrative' => _('Administrative'),
107             'abuse' => _('Abuse'),
108             'billing' => _('Billing'),
109             'registrar' => _('Registrar'),
110             'reseller' => _('Reseller'),
111             'sponsor' => _('Sponsor'),
112             'proxy' => _('Proxy'),
113             'notifications' => _('Notifications'),
114             'noc' => _('NOC'),
115             );
116              
117             my %EVENT_DISPLAY_NAMES = (
118             'registration' => _('Registration'),
119             'reregistration' => _('Reregistration'),
120             'last changed' => _('Last Changed'),
121             'expiration' => _('Expiration'),
122             'deletion' => _('Deletion'),
123             'reinstantiation' => _('Reinstantiation'),
124             'transfer' => _('Transfer'),
125             'locked' => _('Locked'),
126             'unlocked' => _('Unlocked'),
127             'last update of RDAP database' => _('Last update of RDAP database'),
128             'registrar expiration' => _('Registrar expiration'),
129             'enum validation expiration' => _('ENUM validation expiration'),
130             );
131              
132             my %STATUS_DISPLAY_NAMES = (
133             'validated' => _('validated'),
134             'renew prohibited' => _('renew prohibited'),
135             'update prohibited' => _('update prohibited'),
136             'transfer prohibited' => _('transfer prohibited'),
137             'delete prohibited' => _('delete prohibited'),
138             'proxy' => _('proxy'),
139             'private' => _('private'),
140             'removed' => _('removed'),
141             'obscured' => _('obscured'),
142             'associated' => _('associated'),
143             'active' => _('active'),
144             'inactive' => _('inactive'),
145             'locked' => _('locked'),
146             'pending create' => _('pending create'),
147             'pending renew' => _('pending renew'),
148             'pending transfer' => _('pending transfer'),
149             'pending update' => _('pending update'),
150             'pending delete' => _('pending delete'),
151             'add period' => _('add period'),
152             'auto renew period' => _('auto renew period'),
153             'client delete prohibited' => _('client delete prohibited'),
154             'client hold' => _('client hold'),
155             'client renew prohibited' => _('client renew prohibited'),
156             'client transfer prohibited' => _('client transfer prohibited'),
157             'client update prohibited' => _('client update prohibited'),
158             'pending restore' => _('pending restore'),
159             'redemption period' => _('redemption period'),
160             'renew period' => _('renew period'),
161             'server delete prohibited' => _('server delete prohibited'),
162             'server renew prohibited' => _('server renew prohibited'),
163             'server transfer prohibited' => _('server transfer prohibited'),
164             'server update prohibited' => _('server update prohibited'),
165             'server hold' => _('server hold'),
166             'transfer period' => _('transfer period'),
167             'administrative' => _('administrative'),
168             'reserved' => _('reserved'),
169             );
170              
171             my @EVENTS = (
172             'registration',
173             'reregistration',
174             'last changed',
175             'expiration',
176             'deletion',
177             'reinstantiation',
178             'transfer',
179             'locked',
180             'unlocked',
181             'last update of RDAP database',
182             'registrar expiration',
183             'enum validation expiration',
184             );
185              
186             my %EVENT_DISPLAY_ORDER;
187             for (my $i = 0 ; $i < scalar(@EVENTS) ; $i++) {
188             $EVENT_DISPLAY_ORDER{$EVENTS[$i]} = $i;
189             }
190              
191             my %NOTICE_REMARK_TYPE = (
192             'result set truncated due to authorization' => _('result set truncated due to authorization'),
193             'result set truncated due to excessive load' => _('result set truncated due to excessive load'),
194             'result set truncated due to unexplainable reasons' => _('result set truncated due to unexplainable reasons'),
195             'object truncated due to authorization' => _('object truncated due to authorization'),
196             'object truncated due to excessive load' => _('object truncated due to excessive load'),
197             'object truncated due to unexplainable reasons' => _('object truncated due to unexplainable reasons'),
198             'object redacted due to authorization' => _('object redacted due to authorization'),
199             );
200              
201             my %VCARD_KIND_DISPLAY_NAME = (
202             'individual' => _('individual'),
203             'group' => _('group'),
204             'org' => _('org'),
205             'location' => _('location'),
206             'application' => _('application'),
207             'device' => _('device'),
208             );
209              
210             my @VCARD_DISPLAY_ORDER = qw(SOURCE KIND FN TITLE ROLE ORG ADR GEO EMAIL CONTACT-URI SOCIALPROFILE TEL IMPP URL CATEGORIES NOTE);
211             my %VCARD_NODE_NAMES = (
212             ADR => _('Address'),
213             CATEGORIES => _('Categories'),
214             'CONTACT-URI' => _('Contact Link'),
215             EMAIL => _('Email'),
216             FN => _('Name'),
217             GEO => _('Location'),
218             IMPP => _('Messaging'),
219             KIND => _('Kind'),
220             NOTE => _('Note'),
221             ORG => _('Organization'),
222             ROLE => _('Role'),
223             SOCIALPROFILE => _('Profile'),
224             SOURCE => _('Source'),
225             TEL => _('Phone'),
226             TITLE => _('Title'),
227             URL => _('Website'),
228             );
229              
230             my %PUBLIC_ID_DISPLAY_NAME = (
231             'IANA Registrar ID' => _('IANA Registrar ID'),
232             );
233              
234             my @ADR_DISPLAY_ORDER = (ADR_STREET, ADR_CITY, ADR_SP, ADR_PC, ADR_CC);
235             my %ADR_DISPLAY_NAMES = (
236             &ADR_STREET => _('Street'),
237             &ADR_CITY => _('City'),
238             &ADR_SP => _('State/Province'),
239             &ADR_PC => _('Postal Code'),
240             &ADR_CC => _('Country'),
241             );
242              
243             my $json = JSON->new->utf8->canonical->pretty->convert_blessed;
244              
245             my $rdap;
246              
247             my $out = \*STDOUT;
248             my $err = \*STDERR;
249              
250             $out->binmode(':utf8');
251             $err->binmode(':utf8');
252              
253             $Text::Wrap::columns = max((Term::Size::chars)[0], 75);
254             $Text::Wrap::huge = 'overflow';
255              
256             sub main {
257 6     6 0 4442 my $package = shift;
258              
259 6         52 my $rcfile = sprintf(q{%s/.rdapper}, $ENV{HOME});
260 6 50 33     299 if (-e $rcfile && open(my $fh, $rcfile)) {
261 0         0 push(@_, map { chomp ; $_ } $fh->getlines);
  0         0  
  0         0  
262 0         0 $fh->close;
263             }
264              
265 6 50       122 GetOptionsFromArray(\@_, %opts) || $package->show_usage;
266              
267             #
268             # this undocumented behaviour is used to export all the translateable
269             # strings in this file.
270             #
271 6 50       19752 export_strings() if ($strings);
272              
273 6 50       24 $ENV{NET_RDAP_UA_DEBUG} = 1 if ($debug);
274              
275 6   50     62 my $lang = $LH->language_tag || q{en};
276 6         173 $lang =~ s/_/-/g;
277              
278 6         96 $rdap = Net::RDAP->new(
279             'use_cache' => !$bypass,
280             'cache_ttl' => 300,
281             'accept_language' => $lang,
282             );
283              
284 6 50       109 $package->show_version if ($version);
285              
286 6 50 66     78 if ($registry && $registrar) {
    50 66        
287 0         0 $package->error(_("cannot specify both --registry and --registrar, use one or the other."));
288              
289             } elsif ($registry && $both) {
290 0         0 $package->error(_("cannot specify both --registry and --both, use one or the other."));
291              
292             }
293              
294 6   66     31 $registrar ||= $both;
295              
296 6 50 66     23 if (!$registry && !$both) {
297 3         7 $registrar = 1;
298             }
299              
300 6 100       21 $object = shift(@_) if (!$object);
301              
302 6 50 33     34 $package->show_usage if ($help || length($object) < 1);
303              
304 6 100       20 if (!$type) {
305 1 50       28 if ($object =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) { $type = 'ip' }
  0 50       0  
    50          
    50          
    50          
    50          
    50          
306 0         0 elsif ($object =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/) { $type = 'ip' }
307 0         0 elsif ($object =~ /^[0-9a-f:]+:[0-9a-f:]*$/i) { $type = 'ip' }
308 0         0 elsif ($object =~ /^[0-9a-f:]+:[0-9a-f:]*\/\d{1,3}$/i) { $type = 'ip' }
309 0         0 elsif ($object =~ /^asn?\d+$/i) { $type = 'autnum' }
310 0         0 elsif ($object =~ /^(file|https)?:\/\//) { $type = 'url' }
311 0         0 elsif ($object =~ /^([a-z]{2,}|xn--[a-z0-9\-]+)$/i) { $type = 'tld' }
312 1         4 else { $type = 'domain' }
313             }
314              
315 6         12 my %args;
316 6 50       19 ($args{'user'}, $args{'pass'}) = split(/:/, $auth, 2) if ($auth);
317              
318 6 50       19 if ($search) {
319 0         0 $package->search($rdap, $object, $type, %args);
320              
321             } else {
322 6         33 $package->lookup($rdap, $object, $type, %args);
323              
324             }
325             }
326              
327             sub lookup {
328 6     6 0 40 my ($package, $rdap, $object, $type, %args) = @_;
329              
330 6         12 my $response;
331              
332 6 50       36 if ('ip' eq $type) {
    50          
    50          
    0          
    0          
    0          
    0          
333 0         0 my $ip = Net::IP->new($object);
334              
335 0 0       0 $package->error(_("invalid IP address '[_1]'", $object)) unless ($ip);
336              
337 0         0 $response = $rdap->ip($ip, %args);
338              
339 0 0       0 $response = $rdap->fetch($response->domain) if ($reverse);
340              
341             } elsif ('autnum' eq $type) {
342 0         0 my $asn = $object;
343 0         0 $asn =~ s/^asn?//ig;
344              
345 0         0 $response = $rdap->autnum(Net::ASN->new($asn), %args);
346              
347             } elsif ('domain' eq $type) {
348 6         26 $response = $rdap->domain(Net::DNS::Domain->new($package->encode_idn($object)), %args);
349              
350             } elsif ('nameserver' eq $type) {
351 0         0 my $url = Net::RDAP::Registry->get_url(Net::DNS::Domain->new($package->encode_idn($object)));
352              
353             #
354             # munge path
355             #
356 0         0 my $path = $url->path;
357 0         0 $path =~ s!/domain/!/nameserver/!;
358 0         0 $url->path($path);
359              
360 0         0 $response = $rdap->fetch($url, %args);
361              
362             } elsif ('entity' eq $type) {
363 0         0 $response = $rdap->entity($object, %args);
364              
365             } elsif ('tld' eq $type) {
366 0         0 $response = $rdap->fetch(URI->new(IANA_BASE_URL.'domain/'.$package->encode_idn($object)), %args);
367              
368             } elsif ('url' eq $type) {
369 0         0 my $uri = URI->new($object);
370              
371             #
372             # if the path ends with /help then we assume then it's a help query
373             #
374 0 0       0 $args{'class_override'} = 'help' if ('help' eq lc(($uri->path_segments)[-1]));
375              
376 0         0 $response = $rdap->fetch($uri, %args);
377              
378             } else {
379 0         0 $package->error(_("Unable to handle type '[_1]'", $type));
380              
381             }
382              
383 6         6100285 $package->display($response, 0);
384             }
385              
386             sub show_usage {
387 0     0 0 0 my $package = shift;
388              
389 0         0 pod2usage(
390             '-input' => __FILE__,
391             '-verbose' => 99,
392             '-sections' => [qw(SYNOPSIS OPTIONS)],
393             );
394             }
395              
396             sub show_version {
397 0     0 0 0 my $package = shift;
398 0         0 $out->say(sprintf('%s v%s', $package, $VERSION));
399 0         0 exit;
400             }
401              
402             sub search {
403 0     0 0 0 my ($package, $rdap, $object, $type, %args) = @_;
404              
405 0 0       0 if ('domain' eq $type) {
406 0         0 $package->domain_search($rdap, $object, %args);
407              
408             } else {
409 0         0 $package->error(_("current unable to do searches for '[_1]' objects.", $type));
410              
411             }
412             }
413              
414             sub domain_search {
415 0     0 0 0 my ($package, $rdap, $query, %args) = @_;
416              
417 0         0 my @labels = grep { length > 0 } split(/\./, lc($query), 2);
  0         0  
418              
419 0         0 my $prefix = shift(@labels);
420 0   0     0 my $suffix = shift(@labels) || '*';
421              
422 0         0 my $servers = {};
423 0         0 my $zones = {};
424              
425 0         0 foreach my $service (Net::RDAP::Registry->load_registry(Net::RDAP::Registry::DNS_URL)->services) {
426 0         0 foreach my $zone ($service->registries) {
427 0         0 my $url = Net::RDAP::Registry->get_best_url($service->urls);
428              
429 0 0       0 if (!exists($servers->{$url->as_string})) {
430 0         0 $servers->{$url->as_string} = Net::RDAP::Service->new($url);
431             }
432              
433 0         0 $zones->{lc($zone)} = $url->as_string;
434             }
435             }
436              
437 0         0 my @zones = sort(keys(%{$zones}));
  0         0  
438 0 0       0 @zones = grep { lc($suffix) eq $_ || $suffix =~ /\.$_/i } @zones if ($suffix ne '*');
  0 0       0  
439              
440 0         0 foreach my $zone (@zones) {
441 0         0 my $server = $servers->{$zones->{$zone}};
442 0         0 my $result = $server->domains(name => $prefix);
443              
444 0 0       0 if ($result->isa('Net::RDAP::Error')) {
    0          
445 0         0 $package->warning(sprintf('%s.%s: %s %s', $prefix, $zone, $result->errorCode, $result->title));
446              
447             } elsif ($result->isa('Net::RDAP::SearchResult')) {
448 0         0 $package->display_domain_search_results($result);
449              
450             }
451             }
452             }
453              
454             sub display_domain_search_results {
455 0     0 0 0 my ($package, $result) = @_;
456              
457 0         0 foreach my $domain ($result->domains) {
458 0         0 $out->say($domain->name->name);
459             }
460             }
461              
462             sub display_nameserver_search_results {
463 0     0 0 0 my ($package, $result) = @_;
464              
465 0         0 foreach my $nameserver ($result->nameservers) {
466 0         0 $out->say($nameserver->name->name);
467             }
468             }
469              
470             sub display_entity_search_results {
471 0     0 0 0 my ($package, $result) = @_;
472              
473 0         0 foreach my $entity ($result->entities) {
474 0         0 $out->say($entity->handle);
475             }
476             }
477              
478             sub display_search {
479 0     0 0 0 my ($package, $result) = @_;
480              
481 0 0       0 $package->display_domain_search_results($result) if (exists($result->{domainSearchResults}));
482 0 0       0 $package->display_nameserver_search_results($result) if (exists($result->{nameserverSearchResults}));
483 0 0       0 $package->display_entity_search_results($result) if (exists($result->{entitySearchResults}));
484             }
485              
486             sub display {
487 33     33 1 117 my ($package, $object, $indent, $nofatal) = @_;
488              
489 33 50       329 if ($object->isa('Net::RDAP::Error')) {
490 0 0       0 if ($nofatal) {
491 0         0 $package->warning('%03u (%s)', $object->errorCode, $object->title);
492              
493             } else {
494 0         0 $package->error('%03u (%s)', $object->errorCode, $object->title);
495              
496             }
497              
498             } else {
499 33 100       138 my $link = (grep { 'related' eq $_->rel && $_->is_rdap } $object->links)[0];
  21         1278  
500              
501 33 100       780 if ($registrar) {
502             # avoid recursing infinitely
503 3         7 $registrar = undef;
504              
505 3 50       10 if (!$link) {
506 0         0 $package->display($object, $indent);
507              
508             } else {
509 3         14 my $result = $rdap->fetch($link);
510              
511 3 50       1158972 if ($result->isa('Net::RDAP::Error')) {
512 0         0 $package->display($result, $indent, 1);
513              
514 0         0 $package->warning(_('Unable to retrieve registrar record, displaying the registry record...'));
515 0         0 $package->display($object, $indent);
516              
517             } else {
518 3 50       13 $package->display($object, $indent, 1) if ($both);
519              
520 3         50 $package->display($result, $indent);
521              
522             }
523             }
524              
525             } else {
526 30 50       264 if ($raw) {
    50          
527 0         0 $out->print($json->encode($object));
528              
529             } elsif ($object->isa('Net::RDAP::SearchResult')) {
530 0         0 $package->display_search($object);
531              
532             } else {
533 30         125 $package->display_object($object, $indent);
534              
535             }
536             }
537             }
538             }
539              
540             sub display_object {
541 30     30 0 82 my ($package, $object, $indent) = @_;
542              
543 30 50       167 $package->error(_("object does not include the 'objectClassName' properties")) unless ($object->class);
544 30 50       186 $package->error(_("unknown object type '[_1]'", $object->class)) unless ($funcs->{$object->class});
545              
546             #
547             # generic properties
548             #
549 30 100       206 $package->print_kv(_('Object type'), $OBJECT_TYPE_DISPLAY_NAMES->{$object->class}, $indent) if ($indent < 1);
550 30 100 66     3855 $package->print_kv(_('URL'), u($object->self->href), $indent) if ($indent < 1 && $object->self);
551              
552 30 100       2666 if ($object->can('name')) {
553 6         35 my $name = $object->name;
554 6 50       223 $name = $name->name if ($name->can('name'));
555              
556 6 50 66     108 my $xname = $object->can('unicodeName') ? $object->unicodeName || $name : $name;
557              
558 6 50       49 if ($xname ne $name) {
559 0         0 $package->print_kv(_('Name'), sprintf('%s (%s)', uc($xname), uc($name)));
560              
561             } else {
562 6         18 $package->print_kv(_('Name'), uc($name));
563              
564             }
565             }
566              
567             #
568             # object-specific properties
569             #
570 30         1590 $funcs->{$object->class}->($object, $indent);
571              
572             #
573             # more generic properties
574             #
575 30         880 $package->print_events($object, $indent);
576 30         5893 $package->print_status($object, $indent, ('domain' eq $object->class));
577              
578 30         4245 $package->print_entities($object, $indent);
579              
580             #
581             # links, remarks, notices and redactions, unless --short has been passed
582             #
583 30 50       88 if (!$short) {
584 30         94 foreach my $link (grep { 'self' ne $_->rel } $object->links) {
  15         847  
585 6         50 $package->print_link($link, $indent);
586             }
587              
588 30         8773 foreach my $remark ($object->remarks) {
589 0         0 $package->print_remark_or_notice($remark, $indent);
590             }
591              
592 30         730 foreach my $notice ($object->notices) {
593 18         4774 $package->print_remark_or_notice($notice, $indent);
594             }
595              
596 30         3027 my @fields = $object->redactions;
597 30 100       822 if (scalar(@fields) > 0) {
598 3         11 $package->print_kv(_('Redacted Fields'), '', $indent);
599 3         1528 foreach my $field (@fields) {
600 3         20 $out->print(wrap(
601             (INDENT x ($indent + 1)),
602             (INDENT x ($indent + 2)),
603             sprintf("%s %s\n", b('*'), _("[_1] (reason: [_2])", $field->name, $field->reason))
604             ));
605             }
606             }
607             }
608              
609 30 100       5962 $out->print("\n") if ($indent < 1);
610             }
611              
612             sub print_ip {
613 0     0 0 0 my ($package, $ip, $indent) = @_;
614              
615 0 0       0 $package->print_kv(_('Handle'), $ip->handle, $indent) if ($ip->handle);
616 0 0       0 $package->print_kv(_('Version'), $ip->version, $indent) if ($ip->version);
617 0 0       0 $package->print_kv(_('Domain'), u($ip->domain->as_string), $indent) if ($ip->domain);
618 0 0       0 $package->print_kv(_('Type'), $ip->type, $indent) if ($ip->type);
619 0 0       0 $package->print_kv(_('Country'), $ip->country, $indent) if ($ip->country);
620 0 0       0 $package->print_kv(_('Parent'), $ip->parentHandle, $indent) if ($ip->parentHandle);
621 0 0       0 $package->print_kv(_('Range'), $ip->range->prefix, $indent) if ($ip->range);
622              
623 0         0 foreach my $cidr ($ip->cidrs) {
624 0         0 $package->print_kv(_('CIDR'), $cidr->prefix, $indent);
625             }
626             }
627              
628             sub print_asn {
629 0     0 0 0 my ($package, $asn, $indent) = @_;
630              
631 0 0       0 $package->print_kv(_('Handle'), $asn->handle, $indent) if ($asn->handle);
632 0 0 0     0 $package->print_kv(_('Range'), sprintf('%u - %u', $asn->start, $asn->end), $indent) if ($asn->start > 0 && $asn->end > 0 && $asn->end > $asn->start);
      0        
633 0 0       0 $package->print_kv(_('Type'), $asn->type, $indent) if ($asn->type);
634             }
635              
636             sub print_domain {
637 6     6 0 21 my ($package, $domain, $indent) = @_;
638              
639 6 100       31 $package->print_kv(_('Handle'), $domain->handle, $indent) if ($domain->handle);
640              
641 6         1045 foreach my $ns (sort { lc($a->name->name) cmp lc($b->name->name) } $domain->nameservers) {
  6         630  
642 12 50       495 if ($short) {
643 0         0 $package->print_kv(_('Nameserver'), uc($ns->name->name) . ' ' . join(' ', map { $_->short } $ns->addresses), $indent);
  0         0  
644              
645             } else {
646 12         35 $package->print_kv(_('Nameserver'), uc($ns->name->name), $indent);
647 12         4514 $package->print_nameserver($ns, 1+$indent);
648             }
649             }
650              
651 6         89 foreach my $ds ($domain->ds) {
652 3         4818 $package->print_kv(_('DS Record'), $ds->plain, $indent);
653             }
654              
655 6         14334 foreach my $key ($domain->keys) {
656 0         0 $package->print_kv(_('DNSKEY Record'), $key->plain, $indent);
657             }
658              
659 6 50       81 $package->display_artRecord($domain->{'artRecord_record'}, $indent) if ($domain->{'artRecord_record'});
660 6 50       27 $package->display_platform_nameservers($domain->{'platformNS_nameservers'}, $indent) if ($domain->{'platformNS_nameservers'});
661              
662 6 50       28 $package->print_kv(_('Registration Type'), $domain->{'regType_regType'}) if ($domain->{'regType_regType'});
663              
664 6         29 my @types = $domain->dns_ttl_types;
665 6 50       75 $package->display_ttl_values($domain, $indent) if (scalar(@types) > 0);
666             }
667              
668             sub display_artRecord {
669 0     0 0 0 my ($package, $records, $indent) = @_;
670              
671 0         0 $package->print_kv(_('Art Record'), undef, $indent);
672              
673 0         0 foreach my $record (@{$records}) {
  0         0  
674 0         0 $package->print_kv($record->{'name'}, $record->{'value'}, 1+$indent);
675             }
676             }
677              
678             sub display_platform_nameservers {
679 0     0 0 0 my ($package, $nameservers, $indent) = @_;
680              
681 0         0 foreach my $ns (@{$nameservers}) {
  0         0  
682 0         0 $package->print_kv(_('Platform Nameserver'), uc(Net::RDAP::Object::Nameserver->new($ns)->name->name), $indent);
683             }
684             }
685              
686             sub print_entity {
687 24     24 0 53 my ($package, $entity, $indent) = @_;
688              
689 24 50 66     60 $package->print_kv(_('Handle'), $entity->handle, $indent) if ($entity->handle && $indent < 1);
690              
691 24         181 foreach my $id ($entity->ids) {
692 6   33     394 $package->print_kv($PUBLIC_ID_DISPLAY_NAME{$id->type} || $id->type, $id->identifier, $indent);
693             }
694              
695 24         3633 my $jcard = $entity->jcard;
696 24 50       3902 if ($jcard) {
697 24         77 $package->print_jcard($jcard, $indent);
698             }
699             }
700              
701             sub print_jcard {
702 24     24 0 53 my ($package, $jcard, $indent) = @_;
703              
704 24         52 foreach my $ptype (@VCARD_DISPLAY_ORDER) {
705 384         43741 foreach my $property (grep { $_->value } $jcard->properties($ptype)) {
  102         2893  
706 99         511 $package->print_jcard_property($property, $indent);
707             }
708             }
709             }
710              
711             sub print_jcard_property {
712 99     99 0 189 my ($package, $property, $indent) = @_;
713              
714 99 100       1104 if ('ADR' eq uc($property->type)) {
715 12         94 $package->print_jcard_adr($property, $indent);
716              
717             } else {
718 87   33     739 my $label = $VCARD_NODE_NAMES{uc($property->type)} || ucfirst(lc($property->type));
719 87         581 my $value = $property->value;
720              
721 87 100       301 if ('KIND' eq uc($property->type)) {
    100          
722 18   33     134 $value = $VCARD_KIND_DISPLAY_NAME{$value} || $value;
723              
724             } elsif ('TEL' eq uc($property->type)) {
725 18         174 my $types = $property->param('type');
726 18 100       379 $types = [ $types ] if (q{ARRAY} ne ref($types));
727              
728 18 50   18   89 if (any { 'fax' eq lc($_) } @{$types}) {
  18         76  
  18         84  
729 0         0 $label = _('Fax');
730              
731             } else {
732 18         60 $label = _('Phone');
733              
734             }
735             }
736              
737             $package->print_kv(
738 87 100       4193 $label,
739             $property->may_be_uri ? u($value) : $value,
740             $indent
741             );
742             }
743             }
744              
745             sub print_jcard_adr {
746 12     12 0 34 my ($package, $property, $indent) = @_;
747              
748 12         33 $package->print_kv(_('Address'), '', $indent);
749              
750 12 50       4457 if ($property->param('label')) {
751 0         0 $out->print(wrap(
752             INDENT x ($indent + 1),
753             INDENT x ($indent + 1),
754             $property->param('label'),
755             )."\n");
756              
757             } else {
758 12         313 foreach my $i (@ADR_DISPLAY_ORDER) {
759 60 100       10126 if ($property->value->[$i]) {
760 36 50       244 if ('ARRAY' eq ref($property->value->[$i])) {
761 0         0 foreach my $v (grep { $_ } @{$property->value->[$i]}) {
  0         0  
  0         0  
762 0         0 $package->print_kv($ADR_DISPLAY_NAMES{$i}, $v, $indent+1);
763             }
764              
765             } else {
766 36         187 $package->print_kv($ADR_DISPLAY_NAMES{$i}, $property->value->[$i], $indent+1);
767              
768             }
769             }
770             }
771             }
772              
773 12 50       71 if ($property->param('cc')) {
774 12         215 $package->print_kv(_('Country'), $property->param('cc'), $indent+1);
775             }
776             }
777              
778             sub print_nameserver {
779 12     12 0 38 my ($package, $nameserver, $indent) = @_;
780              
781 12 100       55 $package->print_kv(_('Handle'), $nameserver->handle, $indent) if ($nameserver->handle);
782              
783 12         3391 foreach my $ip ($nameserver->addresses) {
784 0         0 $package->print_kv(_('IP Address'), $ip->short, $indent);
785             }
786              
787 12         264 my @types = $nameserver->dns_ttl_types;
788 12 50       162 $package->display_ttl_values($nameserver, $indent) if (scalar(@types) > 0);
789             }
790              
791             sub print_events {
792 30     30 0 89 my ($package, $object, $indent) = @_;
793              
794 30         181 foreach my $event (sort { $EVENT_DISPLAY_ORDER{$a->action} - $EVENT_DISPLAY_ORDER{$b->action} } $object->events) {
  45         1216  
795 42   33     9157 my $action = $EVENT_DISPLAY_NAMES{$event->action} || ucfirst($event->action);
796              
797 42 50       283 if ($event->actor) {
798 0         0 $package->print_kv($action, _('[_1] (by [_2])', scalar($event->date), $event->actor), $indent);
799              
800             } else {
801 42         220 $package->print_kv($action, scalar($event->date).$event->date_tz, $indent);
802              
803             }
804             }
805             }
806              
807             sub print_status {
808 30     30 0 261 my ($package, $object, $indent, $is_domain) = @_;
809              
810 30         125 foreach my $status ($object->status) {
811              
812 9         12420 my $epp = rdap2epp($status);
813 9 50 33     121 if ($epp && $is_domain && !$short) {
      33        
814 9   33     44 my $friendly = $STATUS_DISPLAY_NAMES{$status} || $status;
815 9 50       29 if ($friendly ne $status) {
816 0         0 $package->print_kv(_('Status'), _('[_1] ([_2], EPP: [_3], [_4])', $friendly, $status, $epp, u(sprintf('https://icann.org/epp#%s', $epp))), $indent);
817              
818             } else {
819 9         23 $package->print_kv(_('Status'), _('[_1] (EPP: [_2], [_3])', $status, $epp, u(sprintf('https://icann.org/epp#%s', $epp))), $indent);
820              
821             }
822              
823             } else {
824 0         0 $package->print_kv(_('Status'), $status, $indent);
825              
826             }
827             }
828             }
829              
830             sub print_entities {
831 30     30 0 94 my ($package, $object, $indent) = @_;
832              
833 30         122 my @entities = $object->entities;
834              
835 30         1596 my %seen;
836 30         74 foreach my $role (@ROLE_DISPLAY_NAMES_ORDER) {
837 330         701 for (my $i = 0 ; $i < scalar(@entities) ; $i++) {
838 264 100       764 next if ($seen{$i});
839              
840 96         145 my $entity = $entities[$i];
841 96 100   96   390 if (any { $role eq $_ } $entity->roles) {
  96         979  
842 24         71 $seen{$i} = 1;
843              
844 24   33     56 my $rstring = join(', ', map { _('[_1] Contact', $ROLE_DISPLAY_NAMES{$_} || ucfirst($_)) } $entity->roles);
  24         186  
845              
846 24 100 66     11023 if ($entity->handle && 'not applicable' ne $entity->handle && 'HANDLE REDACTED FOR PRIVACY' ne $entity->handle) {
      66        
847 21         363 $package->print_kv($rstring, $entity->handle, $indent);
848              
849             } else {
850 3         51 $package->print_kv($rstring, undef, $indent);
851              
852             }
853              
854 24         9969 eval {
855 24         132 $package->display($entity, 1+$indent, 1);
856             };
857             }
858             }
859             }
860             }
861              
862             sub print_remark_or_notice {
863 18     18 0 84 my ($package, $thing, $indent) = @_;
864              
865 18 50       127 my $type = ($thing->isa('Net::RDAP::Notice') ? _('Notice') : _('Remark'));
866              
867 18 50       3808 if (1 == scalar($thing->description)) {
868 18   33     199 $package->print_kv($thing->title || $type, ($thing->description)[0], $indent);
869              
870             } else {
871 0   0     0 $package->print_kv($thing->title || $NOTICE_REMARK_TYPE{$thing->type} || $type, , '', $indent);
872              
873 0         0 $out->print(fill(
874             (INDENT x (1+$indent)),
875             (INDENT x (1+$indent)),
876             $thing->description
877             )."\n");
878             }
879              
880 18         67319 foreach my $link ($thing->links) {
881 18         1287 $package->print_link($link, 1+$indent);
882             }
883             }
884              
885             sub print_link {
886 24     24 0 93 my ($package, $link, $indent) = @_;
887              
888 24   50     91 $package->print_kv(
889             $link->title || ('related' eq $link->rel ? 'Link' : ucfirst($link->rel)) || 'Link',
890             u($link->href->as_string),
891             $indent,
892             );
893             }
894              
895             sub display_ttl_values {
896 0     0 0 0 my ($package, $object, $indent) = @_;
897              
898 0         0 $package->print_kv(_('DNS TTL Values'), '', $indent);
899              
900 0         0 foreach my $type ($object->dns_ttl_types) {
901 0         0 $package->print_kv($type, _('[_1]s', $object->dns_ttl($type)), 1+$indent);
902             }
903              
904 0         0 foreach my $remark ($object->dns_ttl_remarks) {
905 0         0 $package->print_remark_or_notice($remark, 1+$indent);
906             }
907             }
908              
909             sub print_kv {
910 315     315 0 58216 my ($package, $name, $value, $indent) = @_;
911              
912 315         1478 $out->print(wrap(
913             (INDENT x $indent),
914             (INDENT x ($indent + 1)),
915             sprintf("%s %s\n", b($name.':'), $value),
916             ));
917             }
918              
919             sub debug {
920 0     0 0 0 my ($package, $fmt, @params) = @_;
921 0 0       0 if ($debug) {
922 0         0 my $str = sprintf(_("Debug: [_1]", $fmt), @params);
923 0         0 $err->say(colourise([qw(magenta)], $str));
924             }
925             }
926              
927             sub info {
928 0     0 0 0 my ($package, $fmt, @params) = @_;
929 0         0 my $str = sprintf(_("Info: [_1]", $fmt), @params);
930 0         0 $err->say(colourise([qw(cyan)], $str));
931             }
932              
933             sub warning {
934 0     0 0 0 my ($package, $fmt, @params) = @_;
935 0         0 my $str = sprintf(_("Warning: [_1]", $fmt), @params);
936 0         0 $err->say(colourise([qw(yellow)], $str));
937             }
938              
939             sub error {
940 0     0 0 0 my ($package, $fmt, @params) = @_;
941 0         0 my $str = sprintf(_("Error: [_1]", $fmt), @params);
942 0         0 $err->say(colourise([qw(red)], $str));
943 0         0 exit 1;
944             }
945              
946             sub colourise {
947 393     393 0 929 my ($cref, $str) = @_;
948              
949 393 50 33     2743 if (-t $out && !$nocolor) {
950 0         0 return colored($cref, $str);
951              
952             } else {
953 393         2647 return $str;
954              
955             }
956             }
957              
958 75     75 0 14880 sub u { colourise([qw(underline)], shift) }
959 318     318 0 985 sub b { colourise([qw(bold)], shift) }
960 350     350   31676 sub _ { decode($LH->encoding, $LH->maketext(@_)) }
961              
962             #
963             # this function uses PPI to parse this file, extract the messages passed to _()
964             # and prints a .po file on STDOUT.
965             #
966             sub export_strings {
967 0     0 0 0 eval {
968 0         0 require PPI;
969              
970 0         0 my $doc = PPI::Document->new(__FILE__);
971 0         0 $doc->prune(q{PPI::Token::Comment});
972 0         0 $doc->prune(q{PPI::Token::Whitespace});
973              
974 0         0 my @msgs;
975              
976 0     0   0 my @nodes = @{$doc->find(sub { 1 })};
  0         0  
  0         0  
977 0         0 for (my $i = 0 ; $i < scalar(@nodes) ; $i++) {
978 0         0 my $node = $nodes[$i];
979              
980 0 0 0     0 if ($node->isa(q{PPI::Token::Magic}) && q{_} eq $node->content) {
981 0         0 my $next = $nodes[$i+1];
982              
983 0 0       0 if ($next->isa(q{PPI::Structure::List})) {
984 0         0 my $msg = ($next->tokens)[1];
985              
986 0 0       0 if (!$msg->isa(q{PPI::Token::Quote})) {
987 0         0 die(sprintf(
988             "%s: first argument to _() must be a string literal",
989             $msg->content,
990             ));
991             }
992              
993 0         0 push(@msgs, $msg->string);
994             }
995             }
996             }
997              
998 0         0 foreach my $msg (uniq(@msgs)) {
999 0         0 printf("msgid \"%s\"\nmsgstr \"\"\n\n", $msg);
1000             }
1001             };
1002              
1003 0         0 exit;
1004             }
1005              
1006             sub encode_idn {
1007 6     6 0 19 my ($package, $name) = @_;
1008 6         78 return Net::IDN::PP->encode($name);
1009             }
1010              
1011             1;
1012              
1013             __END__