File Coverage

blib/lib/Net/IDN/Punycode/PP.pm
Criterion Covered Total %
statement 135 135 100.0
branch 38 48 79.1
condition 1 3 33.3
subroutine 21 21 100.0
pod 0 2 0.0
total 195 209 93.3


line stmt bran cond sub pod time code
1             package Net::IDN::Punycode::PP;
2              
3 2     2   71997 use 5.008;
  2         20  
4              
5 2     2   11 use strict;
  2         3  
  2         54  
6 2     2   12 use utf8;
  2         3  
  2         15  
7 2     2   42 use warnings;
  2         3  
  2         60  
8              
9 2     2   9 use Carp;
  2         4  
  2         107  
10 2     2   20 use Exporter;
  2         10  
  2         250  
11              
12             our $VERSION = "2.500";
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   1110 use integer;
  2         30  
  2         11  
20              
21 2     2   77 use constant BASE => 36;
  2         3  
  2         221  
22 2     2   14 use constant TMIN => 1;
  2         4  
  2         91  
23 2     2   11 use constant TMAX => 26;
  2         3  
  2         82  
24 2     2   38 use constant SKEW => 38;
  2         4  
  2         82  
25 2     2   12 use constant DAMP => 700;
  2         4  
  2         81  
26 2     2   11 use constant INITIAL_BIAS => 72;
  2         4  
  2         78  
27 2     2   10 use constant INITIAL_N => 128;
  2         3  
  2         170  
28              
29 2     2   13 use constant UNICODE_MIN => 0;
  2         4  
  2         115  
30 2     2   12 use constant UNICODE_MAX => 0x10FFFF;
  2         4  
  2         466  
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   732 my($delta, $numpoints, $firsttime) = @_;
38 464 100       1127 $delta = int($firsttime ? $delta / DAMP : $delta / 2);
39 464         641 $delta += int($delta / $numpoints);
40 464         554 my $k = 0;
41 464         815 while ($delta > int(((BASE - TMIN) * TMAX) / 2)) {
42 86         117 $delta /= BASE - TMIN;
43 86         143 $k += BASE;
44             }
45 464         785 return $k + (((BASE - TMIN + 1) * $delta) / ($delta + SKEW));
46             }
47              
48             sub decode_punycode {
49 21 50   21 0 62 die("Usage: Net::IDN::Punycode::decode_punycode(input)") unless @_;
50 2     2   14 no warnings 'utf8';
  2         4  
  2         905  
51              
52 21         41 my $input = shift;
53              
54 21         30 my $n = INITIAL_N;
55 21         34 my $i = 0;
56 21         27 my $bias = INITIAL_BIAS;
57 21         34 my @output;
58              
59 21 50       43 return undef unless defined $input;
60 21 50       45 return '' unless length $input;
61              
62 21 100       135 if($input =~ s/(.*)$Delimiter//os) {
63 10         25 my $base_chars = $1;
64 10 50       48 croak("non-base character in input for decode_punycode")
65             if $base_chars =~ m/[^$BasicRE]/os;
66 10         54 push @output, split //, $base_chars;
67             }
68 21         44 my $code = $input;
69              
70 21 50       73 croak('invalid digit in input for decode_punycode') if $code =~ m/[^$PunyRE]/os;
71              
72 21         59 utf8::downgrade($input); ## handling failure of downgrade is more expensive than
73             ## doing the above regexp w/ utf8 semantics
74              
75 21         45 while(length $code)
76             {
77 232         295 my $oldi = $i;
78 232         271 my $w = 1;
79             LOOP:
80 232         319 for (my $k = BASE; 1; $k += BASE) {
81 431         665 my $cp = substr($code, 0, 1, '');
82 431 50       731 croak("incomplete encoded code point in decode_punycode") if !defined $cp;
83 431         527 my $digit = ord $cp;
84            
85             ## NB: this depends on the PunyRE catching invalid digit characters
86             ## before they turn up here
87             ##
88 431 100       760 $digit = $digit < 0x40 ? $digit + (26-0x30) : ($digit & 0x1f) -1;
89              
90 431         530 $i += $digit * $w;
91 431         525 my $t = $k - $bias;
92 431 100       794 $t = $t < TMIN ? TMIN : $t > TMAX ? TMAX : $t;
    100          
93              
94 431 100       778 last LOOP if $digit < $t;
95 199         281 $w *= (BASE - $t);
96             }
97 232         435 $bias = _adapt($i - $oldi, @output + 1, $oldi == 0);
98 232         368 $n += $i / (@output + 1);
99 232         333 $i = $i % (@output + 1);
100 232 50 33     674 croak('invalid code point') if $n < UNICODE_MIN or $n > UNICODE_MAX;
101 232         476 splice(@output, $i, 0, chr($n));
102 232         446 $i++;
103             }
104 21         133 return join '', @output;
105             }
106              
107             sub encode_punycode {
108 21 50   21 0 11548 die("Usage: Net::IDN::Punycode::encode_punycode(input)") unless @_;
109 2     2   16 no warnings 'utf8';
  2         3  
  2         847  
110              
111 21         41 my $input = shift;
112 21         64 my $input_length = length $input;
113              
114             ## my $output = join '', $input =~ m/([$BasicRE]+)/og; ## slower
115 21         33 my $output = $input; $output =~ s/[^$BasicRE]+//ogs;
  21         168  
116              
117 21         55 my $h = my $bb = length $output;
118 21 100       56 $output .= $Delimiter if $bb > 0;
119 21         58 utf8::downgrade($output); ## no unnecessary use of utf8 semantics
120              
121 21         184 my @input = map ord, split //, $input;
122 21         63 my @chars = sort { $a<=> $b } grep { $_ >= INITIAL_N } @input;
  648         814  
  368         643  
123              
124 21         32 my $n = INITIAL_N;
125 21         30 my $delta = 0;
126 21         29 my $bias = INITIAL_BIAS;
127              
128 21         39 foreach my $m (@chars) {
129 232 100       402 next if $m < $n;
130 178         244 $delta += ($m - $n) * ($h + 1);
131 178         232 $n = $m;
132 178         305 for(my $i = 0; $i < $input_length; $i++)
133             {
134 3432         4195 my $c = $input[$i];
135 3432 100       5383 $delta++ if $c < $n;
136 3432 100       6753 if ($c == $n) {
137 232         281 my $q = $delta;
138             LOOP:
139 232         294 for (my $k = BASE; 1; $k += BASE) {
140 431         563 my $t = $k - $bias;
141 431 100       742 $t = $t < TMIN ? TMIN : $t > TMAX ? TMAX : $t;
    100          
142              
143 431 100       774 last LOOP if $q < $t;
144              
145 199         275 my $o = $t + (($q - $t) % (BASE - $t));
146 199 100       377 $output .= chr $o + ($o < 26 ? 0x61 : 0x30-26);
147              
148 199         322 $q = int(($q - $t) / (BASE - $t));
149             }
150 232 50       409 croak("input exceeds punycode limit") if $q > BASE;
151 232 50       416 $output .= chr $q + ($q < 26 ? 0x61 : 0x30-26);
152              
153 232         404 $bias = _adapt($delta, $h + 1, $h == $bb);
154 232         309 $delta = 0;
155 232         428 $h++;
156             }
157             }
158 178         227 $delta++;
159 178         259 $n++;
160             }
161 21         128 return $output;
162             }
163              
164             1;
165             __END__