File Coverage

blib/lib/Net/IDN/Punycode/PP.pm
Criterion Covered Total %
statement 135 135 100.0
branch 39 48 81.2
condition 2 3 66.6
subroutine 21 21 100.0
pod 0 2 0.0
total 197 209 94.2


line stmt bran cond sub pod time code
1             package Net::IDN::Punycode::PP;
2              
3 14     14   69195 use 5.008;
  14         65  
4              
5 14     14   77 use strict;
  14         28  
  14         291  
6 14     14   86 use utf8;
  14         28  
  14         77  
7 14     14   382 use warnings;
  14         28  
  14         393  
8              
9 14     14   82 use Carp;
  14         31  
  14         894  
10 14     14   105 use Exporter;
  14         33  
  14         1711  
11              
12             our $VERSION = "2.499_20180929";
13              
14             our @ISA = qw(Exporter);
15             our @EXPORT = ();
16             our @EXPORT_OK = qw(encode_punycode decode_punycode);
17             our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
18              
19 14     14   5595 use integer;
  14         266  
  14         76  
20              
21 14     14   474 use constant BASE => 36;
  14         29  
  14         1468  
22 14     14   90 use constant TMIN => 1;
  14         28  
  14         720  
23 14     14   81 use constant TMAX => 26;
  14         27  
  14         693  
24 14     14   83 use constant SKEW => 38;
  14         36  
  14         607  
25 14     14   77 use constant DAMP => 700;
  14         31  
  14         683  
26 14     14   94 use constant INITIAL_BIAS => 72;
  14         35  
  14         672  
27 14     14   82 use constant INITIAL_N => 128;
  14         26  
  14         813  
28              
29 14     14   92 use constant UNICODE_MIN => 0;
  14         29  
  14         739  
30 14     14   87 use constant UNICODE_MAX => 0x10FFFF;
  14         34  
  14         3274  
31              
32             my $Delimiter = chr 0x2D;
33             my $BasicRE = "\x00-\x7f";
34             my $PunyRE = "A-Za-z0-9";
35              
36             sub _adapt {
37 22591     22591   44727 my($delta, $numpoints, $firsttime) = @_;
38 22591 100       42652 $delta = int($firsttime ? $delta / DAMP : $delta / 2);
39 22591         34226 $delta += int($delta / $numpoints);
40 22591         31645 my $k = 0;
41 22591         44980 while ($delta > int(((BASE - TMIN) * TMAX) / 2)) {
42 18448         25316 $delta /= BASE - TMIN;
43 18448         32824 $k += BASE;
44             }
45 22591         43368 return $k + (((BASE - TMIN + 1) * $delta) / ($delta + SKEW));
46             }
47              
48             sub decode_punycode {
49 8400 50   8400 0 22393 die("Usage: Net::IDN::Punycode::decode_punycode(input)") unless @_;
50 14     14   114 no warnings 'utf8';
  14         28  
  14         6462  
51              
52 8400         21800 my $input = shift;
53              
54 8400         13813 my $n = INITIAL_N;
55 8400         12847 my $i = 0;
56 8400         12411 my $bias = INITIAL_BIAS;
57 8400         12872 my @output;
58              
59 8400 50       17943 return undef unless defined $input;
60 8400 50       21300 return '' unless length $input;
61              
62 8400 100       30326 if($input =~ s/(.*)$Delimiter//os) {
63 2060         4624 my $base_chars = $1;
64 2060 50       5684 croak("non-base character in input for decode_punycode")
65             if $base_chars =~ m/[^$BasicRE]/os;
66 2060         7009 push @output, split //, $base_chars;
67             }
68 8400         16257 my $code = $input;
69              
70 8400 50       21218 croak('invalid digit in input for decode_punycode') if $code =~ m/[^$PunyRE]/os;
71              
72 8400         24235 utf8::downgrade($input); ## handling failure of downgrade is more expensive than
73             ## doing the above regexp w/ utf8 semantics
74              
75 8400         20716 while(length $code)
76             {
77 19229         28285 my $oldi = $i;
78 19229         27297 my $w = 1;
79             LOOP:
80 19229         28619 for (my $k = BASE; 1; $k += BASE) {
81 70474         161364 my $cp = substr($code, 0, 1, '');
82 70474 50       130178 croak("incomplete encoded code point in decode_punycode") if !defined $cp;
83 70474         94607 my $digit = ord $cp;
84            
85             ## NB: this depends on the PunyRE catching invalid digit characters
86             ## before they turn up here
87             ##
88 70474 100       124768 $digit = $digit < 0x40 ? $digit + (26-0x30) : ($digit & 0x1f) -1;
89              
90 70474         97163 $i += $digit * $w;
91 70474         97178 my $t = $k - $bias;
92 70474 100       126012 $t = $t < TMIN ? TMIN : $t > TMAX ? TMAX : $t;
    100          
93              
94 70474 100       132126 last LOOP if $digit < $t;
95 51245         80257 $w *= (BASE - $t);
96             }
97 19229         43283 $bias = _adapt($i - $oldi, @output + 1, $oldi == 0);
98 19229         33508 $n += $i / (@output + 1);
99 19229         28619 $i = $i % (@output + 1);
100 19229 100 66     64143 croak('invalid code point') if $n < UNICODE_MIN or $n > UNICODE_MAX;
101 19223         47613 splice(@output, $i, 0, chr($n));
102 19223         44528 $i++;
103             }
104 8394         39459 return join '', @output;
105             }
106              
107             sub encode_punycode {
108 1734 50   1734 0 15389 die("Usage: Net::IDN::Punycode::encode_punycode(input)") unless @_;
109 14     14   126 no warnings 'utf8';
  14         31  
  14         6111  
110              
111 1734         3081 my $input = shift;
112 1734         3302 my $input_length = length $input;
113              
114             ## my $output = join '', $input =~ m/([$BasicRE]+)/og; ## slower
115 1734         2910 my $output = $input; $output =~ s/[^$BasicRE]+//ogs;
  1734         7968  
116              
117 1734         4288 my $h = my $bb = length $output;
118 1734 100       4538 $output .= $Delimiter if $bb > 0;
119 1734         5134 utf8::downgrade($output); ## no unnecessary use of utf8 semantics
120              
121 1734         9931 my @input = map ord, split //, $input;
122 1734         4311 my @chars = sort { $a<=> $b } grep { $_ >= INITIAL_N } @input;
  2745         5053  
  10642         20755  
123              
124 1734         2914 my $n = INITIAL_N;
125 1734         2467 my $delta = 0;
126 1734         2473 my $bias = INITIAL_BIAS;
127              
128 1734         3218 foreach my $m (@chars) {
129 3362 100       6344 next if $m < $n;
130 3257         5709 $delta += ($m - $n) * ($h + 1);
131 3257         4591 $n = $m;
132 3257         6640 for(my $i = 0; $i < $input_length; $i++)
133             {
134 20742         28933 my $c = $input[$i];
135 20742 100       34856 $delta++ if $c < $n;
136 20742 100       43411 if ($c == $n) {
137 3362         5121 my $q = $delta;
138             LOOP:
139 3362         5101 for (my $k = BASE; 1; $k += BASE) {
140 10153         14092 my $t = $k - $bias;
141 10153 100       19484 $t = $t < TMIN ? TMIN : $t > TMAX ? TMAX : $t;
    100          
142              
143 10153 100       19341 last LOOP if $q < $t;
144              
145 6791         10643 my $o = $t + (($q - $t) % (BASE - $t));
146 6791 100       14427 $output .= chr $o + ($o < 26 ? 0x61 : 0x30-26);
147              
148 6791         11869 $q = int(($q - $t) / (BASE - $t));
149             }
150 3362 50       6434 croak("input exceeds punycode limit") if $q > BASE;
151 3362 50       6883 $output .= chr $q + ($q < 26 ? 0x61 : 0x30-26);
152              
153 3362         6960 $bias = _adapt($delta, $h + 1, $h == $bb);
154 3362         5553 $delta = 0;
155 3362         7343 $h++;
156             }
157             }
158 3257         4615 $delta++;
159 3257         5169 $n++;
160             }
161 1734         8140 return $output;
162             }
163              
164             1;
165             __END__