File Coverage

URI/Pure.pm
Criterion Covered Total %
statement 120 125 96.0
branch 75 84 89.2
condition 20 32 62.5
subroutine 16 16 100.0
pod 0 6 0.0
total 231 263 87.8


line stmt bran cond sub pod time code
1             package URI::Pure;
2              
3 1     1   18 use v5.16;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         25  
5 1     1   5 use warnings;
  1         1  
  1         65  
6              
7             our $VERSION = '0.09';
8              
9 1     1   598 use Encode;
  1         10484  
  1         84  
10 1     1   437 use Net::IDN::Punycode qw(encode_punycode decode_punycode);
  1         1370  
  1         842  
11              
12              
13             {
14             # From URI and URI::Escape
15             my $reserved = q(;/?:@&=+$,[]);
16             my $mark = q(-_.!~*'());
17             my $unreserved = "A-Za-z0-9\Q$mark\E";
18             my $uric = quotemeta($reserved) . $unreserved . "%";
19              
20             my %escapes = map { chr($_) => sprintf "%%%02X", $_ } 0 .. 255;
21              
22 2 50   2 0 4 sub uri_unescape { my $str = shift; $str =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg if defined $str; $str }
  2         12  
  63         139  
  2         4  
23              
24 108 50   108 0 132 sub uri_escape { my $str = shift; $str =~ s/([^$uric\#])/ $escapes{$1} /ego if defined $str; $str }
  108         247  
  63         147  
  108         158  
25             }
26              
27              
28             sub _normalize {
29 93     93   131 my ($p) = @_;
30              
31 93 100 100     453 return $p unless $p =~ m/\.\// or $p =~ m/\/\./ or $p =~ m/\/\//;
      66        
32              
33 30 100       72 my $is_abs = $p =~ m/^\// ? 1 : 0;
34 30 100       53 my $is_dot = $p =~ m/^\./ ? 1 : 0;
35 30 100 100     137 my $is_dir = ($p =~ m/\/$/ or $p =~ m/\/\.$/ or $p =~ m/\/\.\.$/) ? 1 : 0;
36              
37 30         86 my @p = split "/", $p;
38 30 100       59 shift @p if $is_abs;
39              
40 30         40 my @n = ();
41 30         44 foreach my $i (@p) {
42 136 100       206 $i or next;
43 134 100       203 if ($i eq "..") {
    100          
44 49 100       56 if ($is_abs) {
45 16         23 pop @n;
46             } else {
47 33 100 100     81 if (@n and $n[0] ne "..") {
48 21         28 pop @n;
49             } else {
50 12         21 push @n, $i;
51             }
52             }
53             } elsif ($i ne ".") {
54 78         112 push @n, $i;
55             }
56             }
57              
58 30 100       83 my $r = join "/", ($is_abs ? "" : ()), @n, ($is_dir ? "" : ());
    100          
59 30 100 100     58 $r ||= "." if $is_dot;
60 30         70 return $r;
61             }
62              
63              
64              
65             sub new {
66 81     81 0 107 my $proto = shift;
67 81   33     239 my $class = ref($proto) || $proto;
68              
69 81         118 my ($uri) = @_;
70 81 50       199 if (Encode::is_utf8($uri)) {
71 0         0 warn "URI must be without utf8 flag: $uri";
72 0         0 return;
73             }
74              
75 81         536 my ($scheme, $authority, $path, $query, $fragment) =
76             $uri =~ m|(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|;
77             # На основе взятого из URI (это также рекомендуется в RFC 3986)
78             # =head1 PARSING URIs WITH REGEXP
79             # Смотри также URI::Split
80              
81 81 100       300 my ($user, $password, $host, $port) = $authority =~ m/^(?:([^:]+)(?::(.+))?\@)?([^:]+)(?::(\d+))?$/ if $authority;
82              
83 81 100       150 $scheme = lc $scheme if $scheme;
84              
85 81 100       102 if ($host) {
86 1 100   1   7 if ($host =~ m/[^\p{ASCII}]/) {
  1         2  
  1         14  
  36         83  
87             $host = join ".", map {
88 1 50       6 if (m/[^\p{ASCII}]/) {
  3         7176  
89 3         15 my $p = decode_utf8 $_;
90 3         123 $p =~ s/\x{202b}//g; # RIGHT-TO-LEFT EMBEDDING
91 3         5 $p =~ s/\x{202c}//g; # POP DIRECTIONAL FORMATTING
92 3         40 join "", "xn--", encode_punycode fc $p;
93             } else {
94 0         0 $_;
95             }
96             } split /\./, $host;
97             } else {
98 35         41 $host = lc $host;
99             }
100             }
101              
102              
103 81 100       171 $path = _normalize($path) if $path;
104              
105 81 100       171 $path = uri_escape($path) if $path;
106              
107 81 100       139 $query = uri_escape($query) if $query;
108              
109 81         421 my $self = {
110             scheme => $scheme,
111             user => $user,
112             password => $password,
113             host => $host,
114             port => $port,
115             path => $path,
116             query => $query,
117             fragment => $fragment,
118             };
119              
120 81         126 bless $self, $class;
121 81         163 return $self;
122             }
123              
124              
125              
126             foreach my $method (qw(scheme user password host port path query fragment)) {
127 1     1   22247 no strict 'refs';
  1         3  
  1         703  
128 78     78   93 *$method = sub { my $self = shift; return $self->{$method} };
  78         305  
129             }
130              
131              
132              
133             sub _as {
134 34     34   35 my $self = shift;
135 34         91 my ($iri) = @_;
136              
137 34 50       62 my @as_string = ($self->{scheme}, ":") if $self->scheme;
138              
139 34 100       83 push @as_string, "//" if $self->{host};
140              
141 34 100       63 if ($self->{user}) {
142 1         3 push @as_string, $self->{user};
143 1 50       7 push @as_string, ":", $self->{password} if $self->{password};
144 1         4 push @as_string, "@";
145             }
146              
147 34 100       63 if (my $host = $self->{host}) {
148 32 100 66     69 if ($iri and $host =~ m/xn--/) {
149             $host = join ".", map {
150 1 50       5 if (m/^xn--/) {
  3         29  
151 3         9 s/^xn--//;
152 3         18 encode_utf8 decode_punycode $_;
153             } else {
154 0         0 $_;
155             }
156             } split /\./, $host;
157             }
158 32         50 push @as_string, $host;
159             }
160              
161 34 100       78 if ($self->{port}) {
162 2 0 33     16 unless (
      0        
      33        
163             ($self->{scheme} eq "http" and $self->{port} == 80) or
164             ($self->{scheme} eq "https" and $self->{port} == 443)
165             ) {
166 0         0 push @as_string, ":", $self->{port};
167             }
168             }
169              
170 34 100       69 if (my $path = $self->{path}) {
171 32 100       52 $path = uri_unescape $path if $iri;
172 32         40 push @as_string, $path;
173             }
174              
175 34 100       59 if (my $query = $self->{query}) {
176 14 100       22 $query = uri_unescape $query if $iri;
177 14         23 push @as_string, "?", $query;
178             }
179              
180 34 100       58 push @as_string, "#", $self->{fragment} if $self->{fragment};
181              
182 34         137 return join "", @as_string;
183             }
184              
185             sub as_string {
186 33     33 0 48 my $self = shift;
187 33         66 $self->_as(0);
188             }
189              
190             sub as_iri {
191 1     1 0 3 my $self = shift;
192 1         4 $self->_as(1);
193             }
194              
195              
196             sub abs {
197 28     28 0 39 my ($self, $base) = @_;
198              
199 28 100       59 return $self if $self->{scheme};
200              
201 26 100       47 if ($self->{host}) {
202 2         4 $self->{scheme} = $base->{scheme};
203 2         5 return $self;
204             }
205              
206 24         36 $self->{scheme} = $base->{scheme};
207 24         35 $self->{user} = $base->{user};
208 24         28 $self->{password} = $base->{password};
209 24         27 $self->{host} = $base->{host};
210 24         31 $self->{port} = $base->{port};
211              
212 24 100       43 if ($self->{path}) {
213 21 100       49 unless ($self->{path} =~ m/^\//) {
214 20         58 my @path = split /\//, $base->{path};
215 20         32 pop @path;
216 20         43 my $path = join "/", @path;
217 20         50 $self->{path} = _normalize($path . "/" . $self->{path});
218             }
219             } else {
220 3         7 $self->{path} = $base->{path};
221 3   66     14 $self->{query} ||= $base->{query};
222             }
223              
224 24         57 return $self;
225             }
226              
227              
228              
229             1;
230              
231             __END__