Skip to content

Commit

Permalink
parse path in Mojo::Path only on demand
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Mar 2, 2013
1 parent 97adf41 commit 868920d
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 49 deletions.
113 changes: 73 additions & 40 deletions lib/Mojo/Path.pm
Expand Up @@ -8,8 +8,6 @@ use overload
use Mojo::Util qw(decode encode url_escape url_unescape);

has charset => 'UTF-8';
has [qw(leading_slash trailing_slash)];
has parts => sub { [] };

sub new { shift->SUPER::new->parse(@_) }

Expand Down Expand Up @@ -37,10 +35,16 @@ sub canonicalize {
}

sub clone {
my $self = shift;
my $clone = Mojo::Path->new;
$clone->$_($self->$_) for qw(charset leading_slash trailing_slash);
return $clone->parts([@{$self->parts}]);
my $self = shift;

my $clone = Mojo::Path->new->charset($self->charset);
if (my $parts = $self->{parts}) {
$clone->{$_} = $self->{$_} for qw(leading_slash trailing_slash);
$clone->{parts} = [@$parts];
}
else { $clone->{path} = $self->{path} }

return $clone;
}

sub contains {
Expand All @@ -55,6 +59,8 @@ sub contains {
return !@$parts;
}

sub leading_slash { shift->_lazy(leading_slash => @_) }

sub merge {
my ($self, $path) = @_;

Expand All @@ -70,18 +76,18 @@ sub merge {

sub parse {
my ($self, $path) = @_;
$self->{path} = $path;
$self->_parse if $self->{parts};
return $self;
}

$path = url_unescape $path // '';
my $charset = $self->charset;
$path = decode($charset, $path) // $path if $charset;
$self->leading_slash($path =~ s!^/!! ? 1 : undef);
$self->trailing_slash($path =~ s!/$!! ? 1 : undef);
sub parts { shift->_lazy(parts => @_) }

return $self->parts([split '/', $path, -1]);
sub to_abs_string {
my $path = shift->to_string;
return $path =~ m!^/! ? $path : "/$path";
}

sub to_abs_string { $_[0]->leading_slash ? "$_[0]" : "/$_[0]" }

sub to_dir {
my $clone = shift->clone;
pop @{$clone->parts} unless $clone->trailing_slash;
Expand All @@ -96,17 +102,44 @@ sub to_route {
sub to_string {
my $self = shift;

my @parts = @{$self->parts};
# Path
my $charset = $self->charset;
if (defined(my $path = $self->{path})) {
$path = encode $charset, $path if $charset;
return url_escape $path, '^A-Za-z0-9\-._~!$&\'()*+,;=%:@/';
}

# Build path
my @parts = @{$self->parts};
@parts = map { encode $charset, $_ } @parts if $charset;
my $path = join '/',
map { url_escape $_, '^A-Za-z0-9\-._~!$&\'()*+,;=:@' } @parts;
$path = "/$path" if $self->leading_slash;
$path = "$path/" if $self->trailing_slash;

return $path;
}

sub trailing_slash { shift->_lazy(trailing_slash => @_) }

sub _lazy {
my ($self, $name) = (shift, shift);
$self->_parse unless $self->{parts};
return $self->{$name} unless @_;
$self->{$name} = shift;
return $self;
}

sub _parse {
my $self = shift;

my $path = url_unescape delete($self->{path}) // '';
my $charset = $self->charset;
$path = decode($charset, $path) // $path if $charset;
$self->{leading_slash} = $path =~ s!^/!! ? 1 : undef;
$self->{trailing_slash} = $path =~ s!/$!! ? 1 : undef;
$self->{parts} = [split '/', $path, -1];
}

1;

=encoding utf8
Expand Down Expand Up @@ -141,30 +174,6 @@ Charset used for encoding and decoding, defaults to C<UTF-8>.
# Disable encoding and decoding
$path->charset(undef);
=head2 leading_slash
my $leading_slash = $path->leading_slash;
$path = $path->leading_slash(1);
Path has a leading slash.
=head2 parts
my $parts = $path->parts;
$path = $path->parts([qw(foo bar baz)]);
The path parts.
# Part with slash
push @{$path->parts}, 'foo/bar';
=head2 trailing_slash
my $trailing_slash = $path->trailing_slash;
$path = $path->trailing_slash(1);
Path has a trailing slash.
=head1 METHODS
L<Mojo::Path> inherits all methods from L<Mojo::Base> and implements the
Expand Down Expand Up @@ -208,6 +217,13 @@ Check if path contains given prefix.
Mojo::Path->new('/foo/bar')->contains('/bar');
Mojo::Path->new('/foo/bar')->contains('/whatever');
=head2 leading_slash
my $leading_slash = $path->leading_slash;
$path = $path->leading_slash(1);
Path has a leading slash.
=head2 merge
$path = $path->merge('/foo/bar');
Expand Down Expand Up @@ -240,6 +256,16 @@ Turn path into an absolute string.
# "/i/%E2%99%A5/mojolicious"
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_abs_string;
=head2 parts
my $parts = $path->parts;
$path = $path->parts([qw(foo bar baz)]);
The path parts.
# Part with slash
push @{$path->parts}, 'foo/bar';
=head2 to_dir
my $dir = $route->to_dir;
Expand Down Expand Up @@ -268,6 +294,13 @@ Turn path into a string.
# "i/%E2%99%A5/mojolicious"
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_string;
=head2 trailing_slash
my $trailing_slash = $path->trailing_slash;
$path = $path->trailing_slash(1);
Path has a trailing slash.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
Expand Down
4 changes: 2 additions & 2 deletions t/mojo/cookiejar.t
Expand Up @@ -286,11 +286,11 @@ $jar->add(
value => 'bar'
)
);
@cookies = $jar->find(Mojo::URL->new('http://kraih.com/foo%28bar'));
@cookies = $jar->find(Mojo::URL->new('http://kraih.com/foo(bar'));
is $cookies[0]->name, 'foo', 'right name';
is $cookies[0]->value, 'bar', 'right value';
is $cookies[1], undef, 'no second cookie';
@cookies = $jar->find(Mojo::URL->new('http://kraih.com/foo%28bar/baz'));
@cookies = $jar->find(Mojo::URL->new('http://kraih.com/foo(bar/baz'));
is $cookies[0]->name, 'foo', 'right name';
is $cookies[0]->value, 'bar', 'right value';
is $cookies[1], undef, 'no second cookie';
Expand Down
26 changes: 20 additions & 6 deletions t/mojo/path.t
Expand Up @@ -66,7 +66,9 @@ is $path->to_abs_string, '/0', 'right result';
# Canonicalizing
$path = Mojo::Path->new(
'/%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd');
is "$path", '//../../../../../../../../../../etc/passwd', 'right result';
is "$path",
'/%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd',
'same path';
is $path->parts->[0], '', 'right part';
is $path->parts->[1], '..', 'right part';
is $path->parts->[2], '..', 'right part';
Expand All @@ -81,8 +83,9 @@ is $path->parts->[10], '..', 'right part';
is $path->parts->[11], 'etc', 'right part';
is $path->parts->[12], 'passwd', 'right part';
is $path->parts->[13], undef, 'no part';
is "$path", '//../../../../../../../../../../etc/passwd', 'normalized path';
is $path->canonicalize, '/../../../../../../../../../../etc/passwd',
'right result';
'canonicalized path';
is $path->parts->[0], '..', 'right part';
is $path->parts->[1], '..', 'right part';
is $path->parts->[2], '..', 'right part';
Expand All @@ -102,7 +105,9 @@ ok !$path->trailing_slash, 'no trailing slash';
# Canonicalizing (alternative)
$path = Mojo::Path->new(
'%2ftest%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd');
is "$path", '/test/../../../../../../../../../etc/passwd', 'right result';
is "$path",
'%2ftest%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd',
'right result';
is $path->parts->[0], 'test', 'right part';
is $path->parts->[1], '..', 'right part';
is $path->parts->[2], '..', 'right part';
Expand All @@ -116,7 +121,9 @@ is $path->parts->[9], '..', 'right part';
is $path->parts->[10], 'etc', 'right part';
is $path->parts->[11], 'passwd', 'right part';
is $path->parts->[12], undef, 'no part';
is $path->canonicalize, '/../../../../../../../../etc/passwd', 'right result';
is "$path", '/test/../../../../../../../../../etc/passwd', 'normalized path';
is $path->canonicalize, '/../../../../../../../../etc/passwd',
'canonicalized path';
is $path->parts->[0], '..', 'right part';
is $path->parts->[1], '..', 'right part';
is $path->parts->[2], '..', 'right part';
Expand All @@ -133,7 +140,7 @@ ok !$path->trailing_slash, 'no trailing slash';

# Canonicalizing (with escaped "%")
$path = Mojo::Path->new('%2ftest%2f..%252f..%2f..%2f..%2f..%2fetc%2fpasswd');
is "$path", '/test/..%252f../../../../etc/passwd', 'right result';
is "$path", '%2ftest%2f..%252f..%2f..%2f..%2f..%2fetc%2fpasswd', 'same path';
is $path->parts->[0], 'test', 'right part';
is $path->parts->[1], '..%2f..', 'right part';
is $path->parts->[2], '..', 'right part';
Expand All @@ -142,7 +149,8 @@ is $path->parts->[4], '..', 'right part';
is $path->parts->[5], 'etc', 'right part';
is $path->parts->[6], 'passwd', 'right part';
is $path->parts->[7], undef, 'no part';
is $path->canonicalize, '/../etc/passwd', 'right result';
is "$path", '/test/..%252f../../../../etc/passwd', 'normalized path';
is $path->canonicalize, '/../etc/passwd', 'canonicalized path';
is $path->parts->[0], '..', 'right part';
is $path->parts->[1], 'etc', 'right part';
is $path->parts->[2], 'passwd', 'right part';
Expand Down Expand Up @@ -281,6 +289,12 @@ is "$path", 'foo%2Fbar', 'right result';
is $path->to_string, 'foo%2Fbar', 'right result';
is $path->to_abs_string, '/foo%2Fbar', 'right result';

# Unchanged path
$path = Mojo::Path->new('/foob%E4r');
is $path->to_string, '/foob%E4r', 'right result';
is $path->to_abs_string, '/foob%E4r', 'right result';
is $path->clone->to_string, '/foob%E4r', 'right result';

# Latin-1
$path = Mojo::Path->new->charset('Latin-1')->parse('/foob%E4r');
is $path->parts->[0], 'foobär', 'right part';
Expand Down
3 changes: 2 additions & 1 deletion t/mojo/url.t
Expand Up @@ -269,7 +269,8 @@ is $url->scheme, 'http', 'right scheme';
is $url->userinfo, undef, 'no userinfo';
is $url->host, 'acme.s3.amazonaws.com', 'right host';
is $url->port, undef, 'no port';
is $url->path, '/mojo/g++-4.2_4.2.3-2ubuntu7_i386.deb', 'right path';
is $url->path, '/mojo%2Fg%2B%2B-4%2E2_4%2E2%2E3-2ubuntu7_i386%2Edeb',
'right path';
ok !$url->query->to_string, 'no query';
is_deeply $url->query->to_hash, {}, 'right structure';
is $url->fragment, undef, 'no fragment';
Expand Down

0 comments on commit 868920d

Please sign in to comment.