File Coverage

blib/lib/Net/DRI/Util.pm
Criterion Covered Total %
statement 195 353 55.2
branch 139 236 58.9
condition 90 179 50.2
subroutine 41 68 60.2
pod 0 59 0.0
total 465 895 51.9


line stmt bran cond sub pod time code
1             ## Domain Registry Interface, Misc. useful functions
2             ##
3             ## Copyright (c) 2005-2016 Patrick Mevzek . All rights reserved.
4             ##
5             ## This file is part of Net::DRI
6             ##
7             ## Net::DRI is free software; you can redistribute it and/or modify
8             ## it under the terms of the GNU General Public License as published by
9             ## the Free Software Foundation; either version 2 of the License, or
10             ## (at your option) any later version.
11             ##
12             ## See the LICENSE file that comes with this distribution for more details.
13             #########################################################################################
14              
15             package Net::DRI::Util;
16              
17 88     88   47794 use utf8;
  88         593  
  88         322  
18 88     88   1964 use strict;
  88         92  
  88         1216  
19 88     88   235 use warnings;
  88         84  
  88         1618  
20              
21 88     88   37445 use Time::HiRes ();
  88         81942  
  88         1754  
22 88     88   41197 use Encode ();
  88         628239  
  88         1703  
23 88     88   35817 use Module::Load;
  88         66655  
  88         441  
24 88     88   3153 use Scalar::Util ();
  88         95  
  88         1133  
25 88     88   26118 use Net::DRI::Exception;
  88         119  
  88         198017  
26              
27             =pod
28              
29             =head1 NAME
30              
31             Net::DRI::Util - Various useful functions for Net::DRI operations
32              
33             =head1 DESCRIPTION
34              
35             Please see the README file for details.
36              
37             =head1 SUPPORT
38              
39             For now, support questions should be sent to:
40              
41             Enetdri@dotandco.comE
42              
43             Please also see the SUPPORT file in the distribution.
44              
45             =head1 SEE ALSO
46              
47             Ehttp://www.dotandco.com/services/software/Net-DRI/E
48              
49             =head1 AUTHOR
50              
51             Patrick Mevzek, Enetdri@dotandco.comE
52              
53             =head1 COPYRIGHT
54              
55             Copyright (c) 2005-2016 Patrick Mevzek .
56             All rights reserved.
57              
58             This program is free software; you can redistribute it and/or modify
59             it under the terms of the GNU General Public License as published by
60             the Free Software Foundation; either version 2 of the License, or
61             (at your option) any later version.
62              
63             See the LICENSE file that comes with this distribution for more details.
64              
65             =cut
66              
67              
68             ####################################################################################################
69              
70             ## See https://www.iso.org/obp/ui/#search , select 'Country codes', then 'Officially assigned', order by Alpha-2 code (last checked on 2015-05-24)
71             ## qw/.A .B .C .D .E .F .G .H .I .J .K .L .M .N .O .P .Q .R .S .T .U .V .W .X .Y .Z
72             our %CCA2=map { $_ => 1 } qw/ AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ/,
73             qw/BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ/,
74             qw/CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ/,
75             qw/ DE DJ DK DM DO DZ/,
76             qw/ EC EE EG EH ER ES ET /,
77             qw/ FI FJ FK FM FO FR /,
78             qw/GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY /,
79             qw/ HK HM HN HR HT HU /,
80             qw/ ID IE IL IM IN IO IQ IR IS IT /,
81             qw/ JE JM JO JP /,
82             qw/ KE KG KH KI KM KN KP KR KW KY KZ/,
83             qw/LA LB LC LI LK LR LS LT LU LV LY /,
84             qw/MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ/,
85             qw/NA NC NE NF NG NI NL NO NP NR NU NZ/,
86             qw/ OM /,
87             qw/PA PE PF PG PH PK PL PM PN PR PS PT PW PY /,
88             qw/QA /,
89             qw/ RE RO RS RU RW /,
90             qw/SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ/,
91             qw/ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ/,
92             qw/UA UG UM US UY UZ/,
93             qw/VA VC VE VG VI VN VU /,
94             qw/ WF WS /,
95             qw/ YE YT /,
96             qw/ZA ZM ZW /;
97              
98             sub all_valid
99             {
100 267     267 0 3064 my (@args)=@_;
101 267         449 foreach (@args)
102             {
103 602 100 66     2499 return 0 unless (defined($_) && (ref($_) || length($_)));
      66        
104             }
105 242         1181 return 1;
106             }
107              
108             sub hash_merge
109             {
110 3     3 0 3 my ($rmaster,$rtoadd)=@_;
111 3         12 while(my ($k,$v)=each(%$rtoadd))
112             {
113 3 50       9 $rmaster->{$k}={} unless exists($rmaster->{$k});
114 3         8 while(my ($kk,$vv)=each(%$v))
115             {
116 18 50       27 $rmaster->{$k}->{$kk}=[] unless exists($rmaster->{$k}->{$kk});
117 18         20 my @t=@$vv;
118 18         12 push @{$rmaster->{$k}->{$kk}},\@t;
  18         69  
119             }
120             }
121 3         6 return;
122             }
123              
124             sub deepcopy ## no critic (Subroutines::RequireFinalReturn)
125             {
126 0     0 0 0 my $in=shift;
127 0 0       0 return $in unless defined $in;
128 0         0 my $ref=ref $in;
129 0 0       0 return $in unless $ref;
130 0         0 my $cname;
131 0 0       0 ($cname,$ref)=($1,$2) if ("$in"=~m/^(\S+)=([A-Z]+)\(0x/);
132              
133 0 0       0 if ($ref eq 'SCALAR')
    0          
    0          
134             {
135 0         0 my $tmp=$$in;
136 0         0 return \$tmp;
137             } elsif ($ref eq 'HASH')
138             {
139 0 0 0     0 my $r={ map { $_ => (defined $in->{$_} && ref $in->{$_}) ? deepcopy($in->{$_}) : $in->{$_} } keys(%$in) };
  0         0  
140 0 0       0 bless($r,$cname) if defined $cname;
141 0         0 return $r;
142             } elsif ($ref eq 'ARRAY')
143             {
144 0 0 0     0 return [ map { (defined $_ && ref $_)? deepcopy($_) : $_ } @$in ];
  0         0  
145             } else
146             {
147 0         0 Net::DRI::Exception::usererr_invalid_parameters('Do not know how to deepcopy '.$in);
148             }
149             }
150              
151             sub link_rs
152             {
153 4     4 0 4 my (@rs)=@_;
154 4         3 my %seen;
155 4         10 foreach my $i (1..$#rs)
156             {
157 0 0       0 $rs[$i-1]->_set_last($rs[$i]) unless exists $seen{$rs[$i]};
158 0         0 $seen{$rs[$i]}=1;
159             }
160 4         17 return $rs[0];
161             }
162              
163             ####################################################################################################
164              
165             sub isint
166             {
167 5     5 0 7 my $in=shift;
168 5 100       32 return ($in=~m/^\d+$/)? 1 : 0;
169             }
170              
171             ## eppcom:roidType
172             sub is_roid
173             {
174 0     0 0 0 my $in=shift;
175 0   0     0 return xml_is_token($in,3,89) && $in=~m/^\w{1,80}-[0-9A-Za-z]{1,8}$/;
176             }
177              
178             sub check_equal
179             {
180 7     7 0 13 my ($input,$ra,$default)=@_;
181 7 100       19 return $default unless defined($input);
182 5 100       14 foreach my $a (ref($ra)? @$ra : ($ra))
183             {
184 6 100       63 return $a if ($a=~m/^${input}$/);
185             }
186 2 100       6 return $default if $default;
187 1         3 return;
188             }
189              
190             sub check_isa
191             {
192 17     17 0 325 my ($what,$isa)=@_;
193 17 100 50     40 Net::DRI::Exception::usererr_invalid_parameters((${what} || 'parameter').' must be a '.$isa.' object') unless $what && is_class($what,$isa);
      66        
194 16         28 return 1;
195             }
196              
197             sub is_class
198             {
199 35     35 0 73 my ($obj,$class)=@_;
200 35 100       30 return eval { $obj->isa($class); } ? 1 : 0;
  35         307  
201             }
202              
203             sub isa_contactset
204             {
205 0     0 0 0 my $cs=shift;
206 0 0 0     0 return (defined $cs && is_class($cs,'Net::DRI::Data::ContactSet') && !$cs->is_empty())? 1 : 0;
207             }
208              
209             sub isa_contact
210             {
211 9     9 0 11 my ($c,$class)=@_;
212 9 50       21 $class='Net::DRI::Data::Contact' unless defined $class;
213 9 50 33     23 return (defined $c && is_class($c,$class))? 1 : 0; ## no way to check if it is empty or not ? Contact->validate() is too strong as it may die, Contact->roid() maybe not ok always
214             }
215              
216             sub isa_hosts
217             {
218 3     3 0 3 my ($h,$emptyok)=@_;
219 3 100       6 $emptyok=0 unless defined $emptyok;
220 3 50 33     9 return (defined $h && is_class($h,'Net::DRI::Data::Hosts') && ($emptyok || !$h->is_empty()) )? 1 : 0;
221             }
222              
223             sub isa_nsgroup
224             {
225 0     0 0 0 my $h=shift;
226 0 0 0     0 return (defined $h && is_class($h,'Net::DRI::Data::Hosts'))? 1 : 0;
227             }
228              
229             sub isa_changes
230             {
231 3     3 0 2 my $c=shift;
232 3 50 33     9 return (defined $c && is_class($c,'Net::DRI::Data::Changes') && !$c->is_empty())? 1 : 0;
233             }
234              
235             sub isa_statuslist
236             {
237 0     0 0 0 my $s=shift;
238 0 0 0     0 return (defined $s && is_class($s,'Net::DRI::Data::StatusList') && !$s->is_empty())? 1 : 0;
239             }
240              
241             sub has_key
242             {
243 211     211 0 288 my ($rh,$key)=@_;
244 211 50 33     880 return 0 unless (defined $key && $key);
245 211 100 33     2055 return 0 unless (defined $rh && (ref $rh eq 'HASH') && exists $rh->{$key} && defined $rh->{$key});
      33        
      66        
246 88         753 return 1;
247             }
248              
249             sub has_contact
250             {
251 0     0 0 0 my $rh=shift;
252 0   0     0 return has_key($rh,'contact') && isa_contactset($rh->{contact});
253             }
254              
255             sub has_ns
256             {
257 1     1 0 5 my $rh=shift;
258 1   33     1 return has_key($rh,'ns') && isa_hosts($rh->{ns});
259             }
260              
261             sub has_duration
262             {
263 1     1 0 2 my $rh=shift;
264 1   33     2 return has_key($rh,'duration') && check_isa($rh->{'duration'},'DateTime::Duration'); ## check_isa throws an Exception if not
265             }
266              
267             sub has_auth
268             {
269 0     0 0 0 my $rh=shift;
270 0 0 0     0 return (has_key($rh,'auth') && ref $rh->{'auth'} eq 'HASH')? 1 : 0;
271             }
272              
273             sub has_status
274             {
275 0     0 0 0 my $rh=shift;
276 0 0 0     0 return (has_key($rh,'status') && isa_statuslist($rh->{status}))? 1 : 0;
277             }
278              
279             ####################################################################################################
280              
281             sub microtime
282             {
283 43     43 0 102 my ($t,$v)=Time::HiRes::gettimeofday();
284 43         177 return $t.sprintf('%06d',$v);
285             }
286              
287             sub fulltime
288             {
289 0     0 0 0 my ($t,$v)=Time::HiRes::gettimeofday();
290 0         0 my @t=localtime($t);
291 0         0 return sprintf('%d-%02d-%02d %02d:%02d:%02d.%06d',1900+$t[5],1+$t[4],$t[3],$t[2],$t[1],$t[0],$v);
292             }
293              
294             ## From EPP, trID=token from 3 to 64 characters
295             sub create_trid_1
296             {
297 11     11 0 26 my ($name)=@_;
298 11         18 my $mt=microtime(); ## length=16
299 11         49 return uc($name).'-'.$$.'-'.$mt;
300             }
301              
302             sub create_params
303             {
304 7     7 0 8 my ($op,$rd)=@_;
305 7 100       18 return {} unless defined $rd;
306 2 50       9 Net::DRI::Exception::usererr_invalid_parameters('last parameter of '.$op.', if defined, must be a ref hash holding extra parameters as needed') unless ref $rd eq 'HASH';
307 2         7 return { %$rd };
308             }
309              
310             ####################################################################################################
311              
312             sub is_hostname ## RFC952/1123
313             {
314 235     235 0 66019 my ($name,$unicode)=@_;
315 235 100       413 return 0 unless defined $name;
316 234 100       364 $unicode=0 unless defined $unicode;
317              
318 234         529 my @d=split(/\./,$name,-1);
319 234         283 foreach my $d (@d)
320             {
321 291 100 66     871 return 0 unless (defined $d && $d ne '');
322 287 100       375 return 0 unless (length $d <= 63);
323 286 100 100     925 return 0 if (($d=~m/^-/) || ($d=~m/-$/));
324 284 100 66     1641 return 0 if (!$unicode && $d=~m/[^A-Za-z0-9\-]/);
325             }
326 33         97 return 1;
327             }
328              
329             sub is_ipv4
330             {
331 39     39 0 381 my ($ip,$checkpublic)=@_;
332              
333 39 100       69 return 0 unless defined $ip;
334 38         164 my (@ip)=($ip=~m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
335 38 100       75 return 0 unless (@ip==4);
336 34         44 foreach my $s (@ip)
337             {
338 133 100 66     384 return 0 unless (($s >= 0) && ($s <= 255));
339             }
340              
341 33 100 66     100 return 1 unless (defined $checkpublic && $checkpublic);
342              
343             ## Check if this IP is public (see RFC3330)
344 32 100       53 return 0 if ($ip[0] == 0); ## 0.x.x.x [ RFC 1700 ]
345 31 100       45 return 0 if ($ip[0] == 10); ## 10.x.x.x [ RFC 1918 ]
346 30 100       45 return 0 if ($ip[0] == 127); ## 127.x.x.x [ RFC 1700 ]
347 29 100 66     53 return 0 if (($ip[0] == 169) && ($ip[1]==254)); ## 169.254.0.0/16 link local
348 28 100 66     58 return 0 if (($ip[0] == 172 ) && ($ip[1]>=16) && ($ip[1]<=31)); ## 172.16.x.x to 172.31.x.x [ RFC 1918 ]
      100        
349 27 100 100     65 return 0 if (($ip[0] == 192 ) && ($ip[1]==0) && ($ip[2]==2)); ## 192.0.2.0/24 TEST-NET
      66        
350 26 100 100     62 return 0 if (($ip[0] == 192 ) && ($ip[1]==168)); ## 192.168.x.x [ RFC 1918 ]
351 25 100 66     44 return 0 if (($ip[0] >= 224) && ($ip[0] < 240 )); ## 224.0.0.0/4 Class D [ RFC 3171]
352 24 50       31 return 0 if ($ip[0] >= 240); ## 240.0.0.0/4 Class E [ RFC 1700 ]
353 24         73 return 1;
354             }
355              
356             ## Inspired by Net::IP which unfortunately requires Perl 5.8
357             sub is_ipv6
358             {
359 12     12 0 12 my ($ip,$checkpublic)=@_;
360 12 50       18 return 0 unless defined $ip;
361              
362 12         29 my (@ip)=split(/:/,$ip);
363 12 50 33     37 return 0 unless ((@ip > 0) && (@ip <= 8));
364 12 50 33     47 return 0 if (($ip=~m/^:[^:]/) || ($ip=~m/[^:]:$/));
365 12 50       36 return 0 if ($ip =~ s/:(?=:)//g > 1);
366              
367             ## We do not allow IPv4 in IPv6
368 12 100       13 return 0 if grep { ! /^[a-f\d]{0,4}$/i } @ip;
  19         85  
369              
370 1 50 33     5 return 1 unless (defined($checkpublic) && $checkpublic);
371              
372             ## Check if this IP is public
373 1         3 my ($ip1,$ip2)=split(/::/,$ip);
374 1   50     12 $ip1=join('',map { sprintf('%04s',$_) } split(/:/,$ip1 || ''));
  8         12  
375 1   50     6 $ip2=join('',map { sprintf('%04s',$_) } split(/:/,$ip2 || ''));
  0         0  
376 1         3 my $wip=$ip1.('0' x (32-length($ip1)-length($ip2))).$ip2; ## 32 chars
377 1         12 my $bip=unpack('B128',pack('H32',$wip)); ## 128-bit array
378              
379             ## RFC 3513 §2.4
380 1 50       3 return 0 if ($bip=~m/^0{127}/); ## unspecified + loopback
381 1 50       3 return 0 if ($bip=~m/^1{7}/); ## multicast + link-local unicast + site-local unicast
382             ## everything else is global unicast,
383             ## but see §4 and http://www.iana.org/assignments/ipv6-address-space
384 1 50       3 return 0 if ($bip=~m/^000/); ## unassigned + reserved (first 6 lines)
385 1 50       7 return 1 if ($bip=~m/^001/); ## global unicast (2000::/3)
386 0         0 return 0; ## everything else is unassigned
387             }
388              
389             ####################################################################################################
390              
391             sub compare_durations
392             {
393 10     10 0 7 my ($dtd1,$dtd2)=@_;
394              
395             ## from DateTime::Duration module, internally are stored: months, days, minutes, seconds and nanoseconds
396             ## those are the keys of the hash ref given by the deltas method
397 10         16 my %d1=$dtd1->deltas();
398 10         57 my %d2=$dtd2->deltas();
399              
400             ## Not perfect, but should be enough for us
401             return (($d1{months} <=> $d2{months}) ||
402             ($d1{days} <=> $d2{days}) ||
403             ($d1{minutes} <=> $d2{minutes}) ||
404             ($d1{seconds} <=> $d2{seconds})
405 10   33     98 );
406             }
407              
408             ####################################################################################################
409              
410             sub xml_is_normalizedstring
411             {
412 10     10 0 518 my ($what,$min,$max)=@_;
413 10         17 my $r=xml_is_string($what,$min,$max);
414 10 100       22 return 0 if $r==0;
415 6 100       13 return 0 if $what=~m/[\r\n\t]/;
416 5         13 return 1;
417             }
418              
419             sub xml_is_string
420             {
421 10     10 0 8 my ($what,$min,$max)=@_;
422 10 100       18 return 0 unless defined $what;
423 9 50       21 return 0 if defined Scalar::Util::reftype($what);
424 9 50       32 return 0 unless $what=~m/^[\x{0009}\x{000A}\x{000D}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/; ## XML Char definition (all Unicode excluding the surrogate blocks, FFFE, and FFFF)
425 9         9 my $l=length $what;
426 9 100 100     26 return 0 if (defined $min && $l < $min);
427 8 100 100     25 return 0 if (defined $max && $l > $max);
428 6         7 return 1;
429             }
430              
431             sub xml_is_token
432             {
433 13     13 0 20 my ($what,$min,$max)=@_;
434              
435 13 100       28 return 0 unless defined $what;
436 12 50       28 return 0 if defined Scalar::Util::reftype($what);
437 12 100       20 return 0 if $what=~m/[\r\n\t]/;
438 11 100       27 return 0 if $what=~m/^\s/;
439 10 100       23 return 0 if $what=~m/\s$/;
440 9 100       17 return 0 if $what=~m/\s\s/;
441              
442 8         35 my $l=length $what;
443 8 100 100     28 return 0 if (defined $min && $l < $min);
444 7 100 100     30 return 0 if (defined $max && $l > $max);
445 5         13 return 1;
446             }
447              
448             sub xml_is_ncname ## xml:id is of this type
449             {
450 0     0 0 0 my ($what)=@_;
451 0 0 0     0 return 0 unless defined($what) && $what;
452 0 0       0 return 0 if defined Scalar::Util::reftype($what);
453 88     88   456 return ($what=~m/^\p{ID_Start}\p{ID_Continue}*$/)
  88         106  
  88         963  
  0         0  
454             }
455              
456 5 100 100 5 0 10 sub verify_ushort { my $in=shift; return (defined($in) && ($in=~m/^\d+$/) && ($in < 65536))? 1 : 0; }
  5         48  
457 5 100 100 5 0 7 sub verify_ubyte { my $in=shift; return (defined($in) && ($in=~m/^\d+$/) && ($in < 256))? 1 : 0; }
  5         50  
458 4 100 100 4 0 6 sub verify_hex { my $in=shift; return (defined($in) && ($in=~m/^[0-9A-F]+$/i))? 1 : 0; }
  4         28  
459             sub verify_int
460             {
461 13     13 0 25 my ($in,$min,$max)=@_;
462 13 100 100     88 return 0 unless defined($in) && ($in=~m/^-?\d+$/);
463 11 100       39 return 0 if ($in < (defined $min ? $min : -2147483648));
    100          
464 8 100       25 return 0 if ($in > (defined $max ? $max : 2147483647));
    100          
465 5         13 return 1;
466             }
467              
468             sub verify_base64
469             {
470 31     31 0 47 my ($in,$min,$max)=@_;
471 31         25 my $b04='[AQgw]';
472 31         25 my $b16='[AEIMQUYcgkosw048]';
473 31         18 my $b64='[A-Za-z0-9+/]';
474 31 100       394 return 0 unless ($in=~m/^(?:(?:$b64 ?$b64 ?$b64 ?$b64 ?)*(?:(?:$b64 ?$b64 ?$b64 ?$b64)|(?:$b64 ?$b64 ?$b16 ?=)|(?:$b64 ?$b04 ?= ?=)))?$/);
475 27 100 100     67 return 0 if (defined $min && (length $in < $min));
476 24 100 100     45 return 0 if (defined $max && (length $in > $max));
477 23         75 return 1;
478             }
479              
480             ## Same in XML and in RFC3066
481             sub xml_is_language
482             {
483 3     3 0 4 my $in=shift;
484 3 50       7 return 0 unless defined $in;
485 3 100       26 return 1 if ($in=~m/^[a-zA-Z]{1,8}(?:-[a-zA-Z0-9]{1,8})*$/);
486 1         3 return 0;
487             }
488              
489             sub xml_is_boolean
490             {
491 6     6 0 8 my $in=shift;
492 6 50       14 return 0 unless defined $in;
493 6 100       30 return 1 if ($in=~m/^(?:1|0|true|false)$/);
494 2         5 return 0;
495             }
496              
497             sub xml_parse_boolean
498             {
499 0     0 0 0 my $in=shift;
500 0         0 return {'true'=>1,1=>1,0=>0,'false'=>0}->{$in};
501             }
502              
503             sub xml_escape
504             {
505 0     0 0 0 my ($in)=@_;
506 0         0 $in=~s/&/&/g;
507 0         0 $in=~s/
508 0         0 $in=~s/>/>/g;
509 0         0 return $in;
510             }
511              
512             sub xml_write
513             {
514 0     0 0 0 my $rd=shift;
515 0         0 my @t;
516 0 0       0 foreach my $d (ref $rd->[0] ? @$rd : ($rd)) ## $d is a node=ref array
517             {
518 0         0 my @c; ## list of children nodes
519             my %attr;
520 0         0 foreach my $e (grep { defined } @$d)
  0         0  
521             {
522 0 0       0 if (ref $e eq 'HASH')
523             {
524 0         0 while(my ($k,$v)=each(%$e)) { $attr{$k}=$v; }
  0         0  
525             } else
526             {
527 0         0 push @c,$e;
528             }
529             }
530 0         0 my $tag=shift(@c);
531 0 0       0 my $attr=keys(%attr)? ' '.join(' ',map { $_.'="'.$attr{$_}.'"' } sort { $a cmp $b } keys %attr) : '';
  0         0  
  0         0  
532 0 0 0     0 if (!@c || (@c==1 && !ref($c[0]) && ($c[0] eq '')))
      0        
      0        
533             {
534 0         0 push @t,'<'.$tag.$attr.'/>';
535             } else
536             {
537 0         0 push @t,'<'.$tag.$attr.'>';
538 0 0 0     0 push @t,(@c==1 && !ref($c[0]))? xml_escape($c[0]) : xml_write(\@c);
539 0         0 push @t,'';
540             }
541             }
542 0         0 return @t;
543             }
544              
545             sub xml_indent
546             {
547 0     0 0 0 my $xml=shift;
548 0         0 chomp $xml;
549 0         0 my $r='';
550              
551 0         0 $xml=~s!(<)!\n$1!g;
552 0         0 $xml=~s!<(\S+)>(.+)\n!<$1>$2!g;
553 0         0 $xml=~s!<(\S+)((?:\s+\S+=['"][^'"]+['"])+)>(.+)\n!<$1$2>$3!g;
554              
555 0         0 my $s=0;
556 0         0 foreach my $m (split(/\n/,$xml))
557             {
558 0 0       0 next if $m=~m/^\s*$/;
559 0 0       0 $s-- if ($m=~m!^$!);
560              
561 0         0 $r.=' ' x $s;
562 0         0 $r.=$m."\n";
563              
564 0 0       0 $s++ if ($m=~m!^<[^>?]+[^/](?:\s+\S+=['"][^'"]+['"])*>$!);
565 0 0       0 $s-- if ($m=~m!^$!);
566             }
567              
568             ## As xml_indent is used during logging, we do a final quick check (spaces should not be relevant anyway)
569             ## This test should probably be dumped as some point in the future when we are confident enough. But we got hit in the past by some subtleties, so...
570 0         0 my $in=$xml;
571 0         0 $in=~s/\s+//g;
572 0         0 my $out=$r;
573 0         0 $out=~s/\s+//g;
574 0 0       0 if ($in ne $out) { Net::DRI::Exception::err_assert('xml_indent failed to do its job, please report !'); }
  0         0  
575              
576 0         0 return $r;
577             }
578              
579             sub xml_list_children
580             {
581 0     0 0 0 my ($node, $name_filter)=@_;
582             ## '*' catch all element nodes being direct children of given node
583 0   0     0 my @r = map { [ $_->localname() || $_->nodeName(),$_ ] } grep { $_->nodeType() == 1 } $node->getChildrenByTagName('*');
  0         0  
  0         0  
584 0 0       0 return @r unless defined $name_filter;
585 0         0 return map { $_->[1] } grep { $_->[0] eq $name_filter } @r;
  0         0  
  0         0  
586             }
587              
588             sub xml_traverse
589             {
590 0     0 0 0 my ($node,$ns,@nodes)=@_;
591 0         0 my $p=sprintf('*[namespace-uri()="%s" and local-name()="%s"]',$ns,shift(@nodes));
592 0 0       0 $p.='/'.join('/',map { '*[local-name()="'.$_.'"]' } @nodes) if @nodes;
  0         0  
593 0         0 my $r=$node->findnodes($p);
594 0 0       0 return unless $r->size();
595 0 0       0 return ($r->size()==1)? $r->get_node(1) : $r->get_nodelist();
596             }
597              
598             sub xml_child_content
599             {
600 0     0 0 0 my ($node,$ns,$what)=@_;
601 0         0 my $list=$node->getChildrenByTagNameNS($ns,$what);
602 0 0       0 return undef unless $list->size()==1; ## no critic (Subroutines::ProhibitExplicitReturnUndef)
603 0         0 my $n=$list->get_node(1);
604 0 0       0 return defined $n ? $n->textContent() : undef;
605             }
606              
607             ####################################################################################################
608              
609             sub remcam
610             {
611 0     0 0 0 my $in=shift;
612 0         0 $in=~s/ID/_id/g;
613 0         0 $in=~s/([A-Z])/_$1/g;
614 0         0 return lc($in);
615             }
616              
617 0 0   0 0 0 sub encode { my ($cs,$data)=@_; return Encode::encode($cs,ref $data? $data->as_string() : $data,1); } ## Will croak on malformed data (a case that should not happen)
  0         0  
618 0     0 0 0 sub encode_utf8 { return encode('UTF-8',$_[0]); } ## no critic (Subroutines::RequireArgUnpacking)
619 0     0 0 0 sub encode_ascii { return encode('ascii',$_[0]); } ## no critic (Subroutines::RequireArgUnpacking)
620 0     0 0 0 sub decode { my ($cs,$data)=@_; return Encode::decode($cs,$data,1); } ## Will croak on malformed data (a case that should not happen)
  0         0  
621 0     0 0 0 sub decode_utf8 { return decode('UTF-8',$_[0]); } ## no critic (Subroutines::RequireArgUnpacking)
622 0     0 0 0 sub decode_ascii { return decode('ascii',$_[0]); } ## no critic (Subroutines::RequireArgUnpacking)
623 0     0 0 0 sub decode_latin1{ return decode('iso-8859-1',$_[0]); } ## no critic (Subroutines::RequireArgUnpacking)
624              
625             sub normalize_name
626             {
627 15     15 0 17 my ($type,$key)=@_;
628 15         20 $type=lc($type);
629             ## contact IDs may be case sensitive...
630             ## Will need to be redone differently with IDNs
631 15 100 66     44 $key=lc $key if ($type eq 'domain' || $type eq 'nsgroup');
632 15 100 66     37 $key=lc $key if ($type eq 'host' && $key=~m/\./); ## last test part is done only to handle the pure mess created by Nominet .UK "EPP" implementation...
633 15         30 return ($type,$key);
634             }
635              
636             ## DateTime object to Zulu time stringified
637             sub dto2zstring
638             {
639 0     0 0 0 my ($dt)=@_;
640 0         0 my $date=$dt->clone()->set_time_zone('UTC');
641 0 0       0 return $date->ymd('-').'T'.$date->hms(':').($date->microsecond() ? '.'.sprintf('%06s',$date->microsecond()) : '').'Z';
642             }
643              
644             ####################################################################################################
645              
646             ## RFC2782
647             ## (Net::DNS rrsort for SRV records does not seem to implement the same algorithm as the one specificied in the RFC,
648             ## as it just does a comparison on priority then weight)
649             sub dns_srv_order
650             {
651 0     0 0 0 my (@args)=@_;
652 0         0 my (@r,%r);
653 0         0 foreach my $ans (@args)
654             {
655 0         0 push @{$r{$ans->priority()}},$ans;
  0         0  
656             }
657 0         0 foreach my $pri (sort { $a <=> $b } keys(%r))
  0         0  
658             {
659 0         0 my @o=@{$r{$pri}};
  0         0  
660 0 0       0 if (@o > 1)
661             {
662 0         0 my $ts=0;
663 0         0 foreach (@o) { $ts+=$_->weight(); }
  0         0  
664 0         0 my $s=0;
665 0         0 @o=map { $s+=$_->weight(); [ $s, $_ ] } (grep { $_->weight() == 0 } @o, grep { $_->weight() > 0 } @o);
  0         0  
  0         0  
  0         0  
  0         0  
666 0         0 my $cs=0;
667 0         0 while(@o > 1)
668             {
669 0         0 my $r=int(rand($ts-$cs+1));
670 0         0 foreach my $i (0..$#o)
671             {
672 0 0       0 next unless $o[$i]->[0] >= $r;
673 0         0 $cs+=$o[$i]->[0];
674 0         0 foreach my $j (($i+1)..$#o) { $o[$j]->[0]-=$o[$i]->[0]; }
  0         0  
675 0         0 push @r,$o[$i]->[1];
676 0         0 splice(@o,$i,1);
677 0         0 last;
678             }
679             }
680             }
681 0         0 push @r,$o[0]->[1];
682             }
683 0         0 return map { [$_->target(),$_->port()] } @r;
  0         0  
684             }
685              
686             ####################################################################################################
687              
688             sub load_module
689             {
690 271     271 0 411 my ($class,$etype)=@_;
691 271         294 my $ok = eval { Module::Load::load($class); 1; };
  271         726  
  204         1985  
692 271 100 50     12766 Net::DRI::Exception::err_failed_load_module($etype,$class,$@ // 'unknown error') if ! defined $ok || ! $ok || $@;
      66        
      66        
693 204         525 return;
694             }
695              
696             ####################################################################################################
697             1;