File Coverage

blib/lib/Net/IDN/PP.pm
Criterion Covered Total %
statement 102 105 97.1
branch 32 34 94.1
condition 6 9 66.6
subroutine 8 8 100.0
pod 0 7 0.0
total 148 163 90.8


line stmt bran cond sub pod time code
1             package Net::IDN::PP 0.02;
2             # ABSTRACT: A pure-Perl implementation of the Punycode algorithm for encoding internationalized domain names (IDNs)
3 1     1   309409 use common::sense;
  1         2  
  1         11  
4              
5              
6             sub encode {
7 9     9 0 8447 my $name = pop;
8 9         58 return join('.', map { encode_label($_) } split(/\./, $name));
  11         25  
9             }
10              
11             sub decode {
12 9     9 0 8661 my $name = pop;
13 9         40 return join('.', map { decode_label($_) } split(/\./, $name));
  11         28  
14             }
15              
16             sub encode_label {
17 11     11 0 51 my $label = lc(pop);
18              
19             # if ASCII-only: return as-is
20 11 100       83 return $label if ($label =~ /^[\x00-\x7F]+$/);
21              
22             # extract code points
23 8         38 my @cps = unpack("U*", $label);
24              
25             # Initialize @output with only the ASCII code points
26 8         16 my @output = map { chr($_) } grep { $_ < 0x80 } @cps;
  23         63  
  34         90  
27              
28 8         15 my $basic_length = scalar(@output);
29 8         19 my $output_str = join('', @output);
30 8 100       27 $output_str .= '-' if ($basic_length > 0);
31              
32             # Punycode parameters
33 8         13 my $n = 128;
34 8         12 my $delta = 0;
35 8         11 my $bias = 72;
36 8         16 my $h = $basic_length;
37 8         15 my $len = scalar(@cps);
38              
39 8         20 while ($h < $len) {
40             # Find the minimum code point >= n
41 11         16 my $m = 0x10FFFF;
42              
43 11         23 for my $cp (@cps) {
44 55 100 100     154 $m = $cp if $cp >= $n && $cp < $m;
45             }
46              
47 11         23 my $inc = ($m - $n) * ($h + 1);
48 11         15 $delta += $inc;
49 11         18 $n = $m;
50              
51 11         19 for my $cp (@cps) {
52 55 100       112 if ($cp < $n) {
    100          
53 38         85 $delta++;
54              
55             } elsif ($cp == $n) {
56 11         14 my $q = $delta;
57              
58 11         21 for (my $k = 36; ; $k += 36) {
59 30         34 my $t;
60              
61 30 100       68 if ($k <= $bias) {
    100          
62 16         23 $t = 1;
63              
64             } elsif ($k >= $bias + 26) {
65 13         22 $t = 26;
66              
67             } else {
68 1         4 $t = $k - $bias;
69              
70             }
71              
72 30 100       91 last if ($q < $t);
73              
74 19         36 my $code = $t + (($q - $t) % (36 - $t));
75 19         40 $output_str .= encode_digit($code);
76 19         53 $q = int(($q - $t) / (36 - $t));
77             }
78              
79 11         23 $output_str .= encode_digit($q);
80 11         27 $bias = adapt($delta, $h + 1, $h == $basic_length);
81 11         19 $delta = 0;
82 11         20 $h++;
83             }
84             }
85              
86 11         15 $delta++;
87 11         48 $n++;
88             }
89              
90 8         66 return q{xn--}.$output_str;
91             }
92              
93             sub encode_digit {
94 30     30 0 43 my $d = shift;
95 30         79 return chr($d + 22 + 75 * ($d < 26)); # 0..25 = a..z, 26..35 = 0..9
96             }
97              
98             sub decode_digit {
99 33     33 0 59 my $ch = lc(shift);
100 33         48 my $cp = ord($ch);
101              
102             # 'a'..'z' => 0..25
103 33 100 66     127 return $cp - 97 if ($cp >= 97 && $cp <= 122);
104              
105             # '0'..'9' => 26..35
106              
107 4 50 33     24 return $cp - 22 if ($cp >= 48 && $cp <= 57);
108              
109 0         0 return 36; # invalid
110             }
111              
112             sub adapt {
113 23     23 0 56 my ($delta, $numpoints, $first_time) = @_;
114 23 100       66 $delta = int($first_time ? $delta / 700 : $delta / 2);
115 23         39 $delta += int($delta / $numpoints);
116              
117 23         41 my $k = 0;
118              
119 23         55 while ($delta > 455) {
120 0         0 $delta = int($delta / 35);
121 0         0 $k += 36;
122             }
123              
124 23         65 return $k + int((36 * $delta) / ($delta + 38));
125             }
126              
127             sub decode_label {
128 11     11 0 23 my $label = shift;
129              
130 11 100       74 return $label unless ($label =~ /^xn--/i);
131              
132 9         27 my $input = lc(substr($label, 4));
133              
134 9         14 my @output;
135 9         23 my $dash_pos = rindex($input, '-');
136 9 100       25 if ($dash_pos != -1) {
137 8         14 my $basic = substr($input, 0, $dash_pos);
138 8         24 @output = map { ord($_) } split(//, $basic);
  28         58  
139 8         24 $input = substr($input, $dash_pos + 1);
140             }
141              
142 9         12 my $n = 128;
143 9         16 my $i = 0;
144 9         11 my $bias = 72;
145              
146 9         32 my $in_idx = 0;
147 9         13 my $in_len = length($input);
148              
149 9         26 while ($in_idx < $in_len) {
150 12         20 my $oldi = $i;
151 12         16 my $w = 1;
152              
153 12         23 for (my $k = 36; ; $k += 36) {
154 33 50       69 last if ($in_idx >= $in_len);
155              
156 33         63 my $c = substr($input, $in_idx++, 1);
157 33         63 my $d = decode_digit($c);
158              
159 33         62 $i += $d * $w;
160              
161 33         78 my $t;
162 33 100       73 if ($k <= $bias) {
    100          
163 18         32 $t = 1;
164              
165             } elsif ($k >= $bias + 26) {
166 14         20 $t = 26;
167              
168             } else {
169 1         4 $t = $k - $bias;
170              
171             }
172              
173 33 100       69 last if ($d < $t);
174 21         41 $w *= (36 - $t);
175             }
176              
177 12         25 my $out_len_plus_1 = scalar(@output) + 1;
178              
179 12         28 $bias = adapt($i - $oldi, $out_len_plus_1, $oldi == 0);
180              
181 12         24 my $q = int($i / $out_len_plus_1);
182              
183 12         18 $n += $q;
184              
185 12         20 $i = $i % $out_len_plus_1;
186              
187 12         25 splice(@output, $i, 0, $n);
188              
189 12         49 $i++;
190             }
191              
192 9         18 return join('', map { chr($_) } @output);
  40         177  
193             }
194              
195             1;
196              
197             __END__