add files
|
1 |
package Mojo::Path; |
2 |
use Mojo::Base -base; |
|
3 |
use overload |
|
4 |
'@{}' => sub { shift->parts }, |
|
5 |
bool => sub {1}, |
|
6 |
'""' => sub { shift->to_string }, |
|
7 |
fallback => 1; |
|
8 | ||
9 |
use Mojo::Util qw(decode encode url_escape url_unescape); |
|
10 | ||
11 |
has charset => 'UTF-8'; |
|
12 | ||
13 |
sub new { shift->SUPER::new->parse(@_) } |
|
14 | ||
15 |
sub canonicalize { |
|
16 |
my $self = shift; |
|
17 | ||
18 |
my @parts; |
|
19 |
for my $part (@{$self->parts}) { |
|
20 | ||
21 |
# ".." |
|
22 |
if ($part eq '..') { |
|
23 |
(@parts && $parts[-1] ne '..') ? pop @parts : push @parts, '..'; |
|
24 |
} |
|
25 | ||
26 |
# Something else than "." |
|
27 |
elsif ($part ne '.' && $part ne '') { push @parts, $part } |
|
28 |
} |
|
29 |
$self->trailing_slash(undef) unless @parts; |
|
30 | ||
31 |
return $self->parts(\@parts); |
|
32 |
} |
|
33 | ||
34 |
sub clone { |
|
35 |
my $self = shift; |
|
36 | ||
37 |
my $clone = $self->new->charset($self->charset); |
|
38 |
if (my $parts = $self->{parts}) { |
|
39 |
$clone->{$_} = $self->{$_} for qw(leading_slash trailing_slash); |
|
40 |
$clone->{parts} = [@$parts]; |
|
41 |
} |
|
42 |
else { $clone->{path} = $self->{path} } |
|
43 | ||
44 |
return $clone; |
|
45 |
} |
|
46 | ||
47 |
sub contains { |
|
48 |
my ($self, $path) = @_; |
|
49 |
return $path eq '/' || $self->to_route =~ m!^\Q$path\E(?:/|$)!; |
|
50 |
} |
|
51 | ||
52 |
sub leading_slash { shift->_parse(leading_slash => @_) } |
|
53 | ||
54 |
sub merge { |
|
55 |
my ($self, $path) = @_; |
|
56 | ||
57 |
# Replace |
|
58 |
return $self->parse($path) if $path =~ m!^/!; |
|
59 | ||
60 |
# Merge |
|
61 |
pop @{$self->parts} unless $self->trailing_slash; |
|
62 |
$path = $self->new($path); |
|
63 |
push @{$self->parts}, @{$path->parts}; |
|
64 |
return $self->trailing_slash($path->trailing_slash); |
|
65 |
} |
|
66 | ||
67 |
sub parse { |
|
68 |
my $self = shift; |
|
69 |
$self->{path} = shift; |
|
70 |
delete @$self{qw(leading_slash parts trailing_slash)}; |
|
71 |
return $self; |
|
72 |
} |
|
73 | ||
74 |
sub parts { shift->_parse(parts => @_) } |
|
75 | ||
76 |
sub to_abs_string { |
|
77 |
my $path = shift->to_string; |
|
78 |
return $path =~ m!^/! ? $path : "/$path"; |
|
79 |
} |
|
80 | ||
81 |
sub to_dir { |
|
82 |
my $clone = shift->clone; |
|
83 |
pop @{$clone->parts} unless $clone->trailing_slash; |
|
84 |
return $clone->trailing_slash(!!@{$clone->parts}); |
|
85 |
} |
|
86 | ||
87 |
sub to_route { |
|
88 |
my $clone = shift->clone; |
|
89 |
my $route = join '/', @{$clone->parts}; |
|
90 |
return "/$route" . ($clone->trailing_slash ? '/' : ''); |
|
91 |
} |
|
92 | ||
93 |
sub to_string { |
|
94 |
my $self = shift; |
|
95 | ||
96 |
# Path |
|
97 |
my $charset = $self->charset; |
|
98 |
if (defined(my $path = $self->{path})) { |
|
99 |
$path = encode $charset, $path if $charset; |
|
100 |
return url_escape $path, '^A-Za-z0-9\-._~!$&\'()*+,;=%:@/'; |
|
101 |
} |
|
102 | ||
103 |
# Build path |
|
104 |
my @parts = @{$self->parts}; |
|
105 |
@parts = map { encode $charset, $_ } @parts if $charset; |
|
106 |
my $path = join '/', |
|
107 |
map { url_escape $_, '^A-Za-z0-9\-._~!$&\'()*+,;=:@' } @parts; |
|
108 |
$path = "/$path" if $self->leading_slash; |
|
109 |
$path = "$path/" if $self->trailing_slash; |
|
110 |
return $path; |
|
111 |
} |
|
112 | ||
113 |
sub trailing_slash { shift->_parse(trailing_slash => @_) } |
|
114 | ||
115 |
sub _parse { |
|
116 |
my ($self, $name) = (shift, shift); |
|
117 | ||
118 |
unless ($self->{parts}) { |
|
119 |
my $path = url_unescape delete($self->{path}) // ''; |
|
120 |
my $charset = $self->charset; |
|
121 |
$path = decode($charset, $path) // $path if $charset; |
|
122 |
$self->{leading_slash} = $path =~ s!^/!!; |
|
123 |
$self->{trailing_slash} = $path =~ s!/$!!; |
|
124 |
$self->{parts} = [split '/', $path, -1]; |
|
125 |
} |
|
126 | ||
127 |
return $self->{$name} unless @_; |
|
128 |
$self->{$name} = shift; |
|
129 |
return $self; |
|
130 |
} |
|
131 | ||
132 |
1; |
|
133 | ||
134 |
=encoding utf8 |
|
135 | ||
136 |
=head1 NAME |
|
137 | ||
138 |
Mojo::Path - Path |
|
139 | ||
140 |
=head1 SYNOPSIS |
|
141 | ||
142 |
use Mojo::Path; |
|
143 | ||
144 |
# Parse |
|
145 |
my $path = Mojo::Path->new('/foo%2Fbar%3B/baz.html'); |
|
146 |
say $path->[0]; |
|
147 | ||
148 |
# Build |
|
149 |
my $path = Mojo::Path->new('/i/♥'); |
|
150 |
push @$path, 'mojolicious'; |
|
151 |
say "$path"; |
|
152 | ||
153 |
=head1 DESCRIPTION |
|
154 | ||
155 |
L<Mojo::Path> is a container for paths used by L<Mojo::URL>. |
|
156 | ||
157 |
=head1 ATTRIBUTES |
|
158 | ||
159 |
L<Mojo::Path> implements the following attributes. |
|
160 | ||
161 |
=head2 charset |
|
162 | ||
163 |
my $charset = $path->charset; |
|
164 |
$path = $path->charset('UTF-8'); |
|
165 | ||
166 |
Charset used for encoding and decoding, defaults to C<UTF-8>. |
|
167 | ||
168 |
# Disable encoding and decoding |
|
169 |
$path->charset(undef); |
|
170 | ||
171 |
=head1 METHODS |
|
172 | ||
173 |
L<Mojo::Path> inherits all methods from L<Mojo::Base> and implements the |
|
174 |
following new ones. |
|
175 | ||
176 |
=head2 new |
|
177 | ||
178 |
my $path = Mojo::Path->new; |
|
179 |
my $path = Mojo::Path->new('/foo%2Fbar%3B/baz.html'); |
|
180 | ||
181 |
Construct a new L<Mojo::Path> object and L</"parse"> path if necessary. |
|
182 | ||
183 |
=head2 canonicalize |
|
184 | ||
185 |
$path = $path->canonicalize; |
|
186 | ||
187 |
Canonicalize path. |
|
188 | ||
189 |
# "/foo/baz" |
|
190 |
Mojo::Path->new('/foo/./bar/../baz')->canonicalize; |
|
191 | ||
192 |
# "/../baz" |
|
193 |
Mojo::Path->new('/foo/../bar/../../baz')->canonicalize; |
|
194 | ||
195 |
=head2 clone |
|
196 | ||
197 |
my $clone = $path->clone; |
|
198 | ||
199 |
Clone path. |
|
200 | ||
201 |
=head2 contains |
|
202 | ||
203 |
my $bool = $path->contains('/i/♥/mojolicious'); |
|
204 | ||
205 |
Check if path contains given prefix. |
|
206 | ||
207 |
# True |
|
208 |
Mojo::Path->new('/foo/bar')->contains('/'); |
|
209 |
Mojo::Path->new('/foo/bar')->contains('/foo'); |
|
210 |
Mojo::Path->new('/foo/bar')->contains('/foo/bar'); |
|
211 | ||
212 |
# False |
|
213 |
Mojo::Path->new('/foo/bar')->contains('/f'); |
|
214 |
Mojo::Path->new('/foo/bar')->contains('/bar'); |
|
215 |
Mojo::Path->new('/foo/bar')->contains('/whatever'); |
|
216 | ||
217 |
=head2 leading_slash |
|
218 | ||
219 |
my $bool = $path->leading_slash; |
|
220 |
$path = $path->leading_slash($bool); |
|
221 | ||
222 |
Path has a leading slash. Note that this method will normalize the path and |
|
223 |
that C<%2F> will be treated as C</> for security reasons. |
|
224 | ||
225 |
=head2 merge |
|
226 | ||
227 |
$path = $path->merge('/foo/bar'); |
|
228 |
$path = $path->merge('foo/bar'); |
|
229 |
$path = $path->merge(Mojo::Path->new('foo/bar')); |
|
230 | ||
231 |
Merge paths. Note that this method will normalize both paths if necessary and |
|
232 |
that C<%2F> will be treated as C</> for security reasons. |
|
233 | ||
234 |
# "/baz/yada" |
|
235 |
Mojo::Path->new('/foo/bar')->merge('/baz/yada'); |
|
236 | ||
237 |
# "/foo/baz/yada" |
|
238 |
Mojo::Path->new('/foo/bar')->merge('baz/yada'); |
|
239 | ||
240 |
# "/foo/bar/baz/yada" |
|
241 |
Mojo::Path->new('/foo/bar/')->merge('baz/yada'); |
|
242 | ||
243 |
=head2 parse |
|
244 | ||
245 |
$path = $path->parse('/foo%2Fbar%3B/baz.html'); |
|
246 | ||
247 |
Parse path. |
|
248 | ||
249 |
=head2 to_abs_string |
|
250 | ||
251 |
my $str = $path->to_abs_string; |
|
252 | ||
253 |
Turn path into an absolute string. |
|
254 | ||
255 |
# "/i/%E2%99%A5/mojolicious" |
|
256 |
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_abs_string; |
|
257 |
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_abs_string; |
|
258 | ||
259 |
=head2 parts |
|
260 | ||
261 |
my $parts = $path->parts; |
|
262 |
$path = $path->parts([qw(foo bar baz)]); |
|
263 | ||
264 |
The path parts. Note that this method will normalize the path and that C<%2F> |
|
265 |
will be treated as C</> for security reasons. |
|
266 | ||
267 |
# Part with slash |
|
268 |
push @{$path->parts}, 'foo/bar'; |
|
269 | ||
270 |
=head2 to_dir |
|
271 | ||
272 |
my $dir = $route->to_dir; |
|
273 | ||
274 |
Clone path and remove everything after the right-most slash. |
|
275 | ||
276 |
# "/i/%E2%99%A5/" |
|
277 |
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_dir->to_abs_string; |
|
278 | ||
279 |
# "i/%E2%99%A5/" |
|
280 |
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_dir->to_abs_string; |
|
281 | ||
282 |
=head2 to_route |
|
283 | ||
284 |
my $route = $path->to_route; |
|
285 | ||
286 |
Turn path into a route. |
|
287 | ||
288 |
# "/i/♥/mojolicious" |
|
289 |
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_route; |
|
290 |
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_route; |
|
291 | ||
292 |
=head2 to_string |
|
293 | ||
294 |
my $str = $path->to_string; |
|
295 |
my $str = "$path"; |
|
296 | ||
297 |
Turn path into a string. |
|
298 | ||
299 |
# "/i/%E2%99%A5/mojolicious" |
|
300 |
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_string; |
|
301 | ||
302 |
# "i/%E2%99%A5/mojolicious" |
|
303 |
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_string; |
|
304 | ||
305 |
=head2 trailing_slash |
|
306 | ||
307 |
my $bool = $path->trailing_slash; |
|
308 |
$path = $path->trailing_slash($bool); |
|
309 | ||
310 |
Path has a trailing slash. Note that this method will normalize the path and |
|
311 |
that C<%2F> will be treated as C</> for security reasons. |
|
312 | ||
313 |
=head1 PATH PARTS |
|
314 | ||
315 |
Direct array reference access to path parts is also possible. Note that this |
|
316 |
will normalize the path and that C<%2F> will be treated as C</> for security |
|
317 |
reasons. |
|
318 | ||
319 |
say $path->[0]; |
|
320 |
say for @$path; |
|
321 | ||
322 |
=head1 SEE ALSO |
|
323 | ||
324 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
325 | ||
326 |
=cut |