File Coverage

blib/lib/Net/IDN/Punycode/PP.pm
Criterion Covered Total %
statement 129 129 100.0
branch 38 48 79.1
condition 1 3 33.3
subroutine 19 19 100.0
pod 0 2 0.0
total 187 201 93.0


line stmt bran cond sub pod time code
1             package Net::IDN::Punycode::PP;
2              
3 2     2   69859 use 5.006;
  2         17  
4              
5 2     2   14 use strict;
  2         4  
  2         52  
6 2     2   11 use utf8;
  2         4  
  2         11  
7 2     2   42 use warnings;
  2         5  
  2         60  
8              
9 2     2   11 use Carp;
  2         3  
  2         103  
10 2     2   24 use Exporter;
  2         5  
  2         253  
11              
12             our $VERSION = "1.101";
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 2     2   1017 use integer;
  2         28  
  2         11  
20              
21 2     2   69 use constant BASE => 36;
  2         3  
  2         166  
22 2     2   14 use constant TMIN => 1;
  2         4  
  2         88  
23 2     2   13 use constant TMAX => 26;
  2         3  
  2         83  
24 2     2   36 use constant SKEW => 38;
  2         4  
  2         85  
25 2     2   10 use constant DAMP => 700;
  2         3  
  2         81  
26 2     2   10 use constant INITIAL_BIAS => 72;
  2         4  
  2         81  
27 2     2   10 use constant INITIAL_N => 128;
  2         5  
  2         122  
28              
29 2     2   12 use constant UNICODE_MIN => 0;
  2         4  
  2         94  
30 2     2   12 use constant UNICODE_MAX => 0x10FFFF;
  2         3  
  2         1954  
31              
32             my $Delimiter = chr 0x2D;
33             my $BasicRE = "\x00-\x7f";
34             my $PunyRE = "A-Za-z0-9";
35              
36             sub _adapt {
37 464     464   772 my($delta, $numpoints, $firsttime) = @_;
38 464 100       801 $delta = int($firsttime ? $delta / DAMP : $delta / 2);
39 464         613 $delta += int($delta / $numpoints);
40 464         601 my $k = 0;
41 464         815 while ($delta > int(((BASE - TMIN) * TMAX) / 2)) {
42 86         116 $delta /= BASE - TMIN;
43 86         148 $k += BASE;
44             }
45 464         841 return $k + (((BASE - TMIN + 1) * $delta) / ($delta + SKEW));
46             }
47              
48             sub decode_punycode {
49 21 50   21 0 59 die("Usage: Net::IDN::Punycode::decode_punycode(input)") unless @_;
50              
51 21         43 my $input = shift;
52              
53 21         31 my $n = INITIAL_N;
54 21         32 my $i = 0;
55 21         32 my $bias = INITIAL_BIAS;
56 21         32 my @output;
57              
58 21 50       43 return undef unless defined $input;
59 21 50       39 return '' unless length $input;
60              
61 21 100       118 if($input =~ s/(.*)$Delimiter//os) {
62 10         27 my $base_chars = $1;
63 10 50       44 croak("non-base character in input for decode_punycode")
64             if $base_chars =~ m/[^$BasicRE]/os;
65 10         56 push @output, split //, $base_chars;
66             }
67 21         40 my $code = $input;
68              
69 21 50       75 croak('invalid digit in input for decode_punycode') if $code =~ m/[^$PunyRE]/os;
70              
71 21         58 utf8::downgrade($input); ## handling failure of downgrade is more expensive than
72             ## doing the above regexp w/ utf8 semantics
73              
74 21         48 while(length $code)
75             {
76 232         324 my $oldi = $i;
77 232         288 my $w = 1;
78             LOOP:
79 232         310 for (my $k = BASE; 1; $k += BASE) {
80 431         711 my $cp = substr($code, 0, 1, '');
81 431 50       701 croak("incomplete encoded code point in decode_punycode") if !defined $cp;
82 431         545 my $digit = ord $cp;
83            
84             ## NB: this depends on the PunyRE catching invalid digit characters
85             ## before they turn up here
86             ##
87 431 100       736 $digit = $digit < 0x40 ? $digit + (26-0x30) : ($digit & 0x1f) -1;
88              
89 431         582 $i += $digit * $w;
90 431         558 my $t = $k - $bias;
91 431 100       750 $t = $t < TMIN ? TMIN : $t > TMAX ? TMAX : $t;
    100          
92              
93 431 100       836 last LOOP if $digit < $t;
94 199         288 $w *= (BASE - $t);
95             }
96 232         452 $bias = _adapt($i - $oldi, @output + 1, $oldi == 0);
97 232         370 $n += $i / (@output + 1);
98 232         343 $i = $i % (@output + 1);
99 232 50 33     700 croak('invalid code point') if $n < UNICODE_MIN or $n > UNICODE_MAX;
100 232         505 splice(@output, $i, 0, chr($n));
101 232         454 $i++;
102             }
103 21         147 return join '', @output;
104             }
105              
106             sub encode_punycode {
107 21 50   21 0 11626 die("Usage: Net::IDN::Punycode::encode_punycode(input)") unless @_;
108              
109 21         41 my $input = shift;
110 21         46 my $input_length = length $input;
111              
112             ## my $output = join '', $input =~ m/([$BasicRE]+)/og; ## slower
113 21         40 my $output = $input; $output =~ s/[^$BasicRE]+//ogs;
  21         164  
114              
115 21         52 my $h = my $bb = length $output;
116 21 100       56 $output .= $Delimiter if $bb > 0;
117 21         62 utf8::downgrade($output); ## no unnecessary use of utf8 semantics
118              
119 21         188 my @input = map ord, split //, $input;
120 21         64 my @chars = sort { $a<=> $b } grep { $_ >= INITIAL_N } @input;
  648         877  
  368         611  
121              
122 21         34 my $n = INITIAL_N;
123 21         39 my $delta = 0;
124 21         33 my $bias = INITIAL_BIAS;
125              
126 21         42 foreach my $m (@chars) {
127 232 100       398 next if $m < $n;
128 178         256 $delta += ($m - $n) * ($h + 1);
129 178         238 $n = $m;
130 178         319 for(my $i = 0; $i < $input_length; $i++)
131             {
132 3432         4429 my $c = $input[$i];
133 3432 100       5607 $delta++ if $c < $n;
134 3432 100       7016 if ($c == $n) {
135 232         305 my $q = $delta;
136             LOOP:
137 232         332 for (my $k = BASE; 1; $k += BASE) {
138 431         573 my $t = $k - $bias;
139 431 100       809 $t = $t < TMIN ? TMIN : $t > TMAX ? TMAX : $t;
    100          
140              
141 431 100       810 last LOOP if $q < $t;
142              
143 199         301 my $o = $t + (($q - $t) % (BASE - $t));
144 199 100       391 $output .= chr $o + ($o < 26 ? 0x61 : 0x30-26);
145              
146 199         304 $q = int(($q - $t) / (BASE - $t));
147             }
148 232 50       385 croak("input exceeds punycode limit") if $q > BASE;
149 232 50       433 $output .= chr $q + ($q < 26 ? 0x61 : 0x30-26);
150              
151 232         419 $bias = _adapt($delta, $h + 1, $h == $bb);
152 232         332 $delta = 0;
153 232         478 $h++;
154             }
155             }
156 178         232 $delta++;
157 178         271 $n++;
158             }
159 21         127 return $output;
160             }
161              
162             1;
163             __END__