File Coverage

blib/lib/Net/DRI/Util.pm
Criterion Covered Total %
statement 190 343 55.3
branch 137 228 60.0
condition 91 179 50.8
subroutine 40 67 59.7
pod 0 59 0.0
total 458 876 52.2


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