Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[io grant] Implement IO::Path.child-secure
The original plan to make .child() resolve and check for secureness
was changed to leave .child() as is and implement .child-secure instead

There is a lot of ecosystem usage of .child(), and there's no real
path for changing it, since the proposed .concat-with() is not available
in older than bleeding-edge compilers. Another concern is the secureness
of .child would silently go away in older compilers.

Per discussion[^1], it was decided to leave .child as is, and add another
method that provides the secure version. The .concat-with will be simply
an alias to .child to make code read nicer when the .child-ed paths
aren't really children (N.B.: it was also decided to rename .concat-with
to .add)

[1] https://irclog.perlgeek.de/perl6-dev/2017-04-16#i_14435663
  • Loading branch information
zoffixznet committed Apr 16, 2017
1 parent b8458d3 commit 1887114
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/core/Exception.pm
Expand Up @@ -556,6 +556,14 @@ my class X::IO::Cwd does X::IO {
}
}

my class X::IO::NotAChild does X::IO {
has $.path;
has $.child;
method message() {
"Path {$.child.perl} is not a child of path {$.path.perl}"
}
}

my class X::IO::Resolve does X::IO {
has $.path;
method message() { "Failed to completely resolve {$.path.perl}" }
Expand Down
24 changes: 24 additions & 0 deletions src/core/IO/Path.pm
Expand Up @@ -335,6 +335,30 @@ my class IO::Path is Cool does IO {
self.bless: :path($!SPEC.join: '', $!path, child), :$!SPEC, :$!CWD
}

method child-secure (IO::Path:D: \child) {
# The goal of this method is to guarantee the resultant child path is
# inside the invocant. We resolve the path completely, so for that to
# happen, the kid cannot be inside some currently non-existent dirs, so
# this method will fail with X::IO::Resolve in those cases. To find out
# if the kid is in fact a kid, we fully-resolve the kid and the
# invocant. Then, we append a dir separator to invocant's .absolute and
# check if the kid's .absolute starts with that string.
nqp::if(
nqp::istype((my $kid := self.child(child).resolve: :completely),
Failure),
$kid, # we failed to resolve the kid, return the Failure
nqp::if(
nqp::istype((my $res-self := self.resolve: :completely), Failure),
$res-self, # failed to resolve invocant, return the Failure
nqp::if(
nqp::iseq_s(
($_ := nqp::concat($res-self.absolute, $!SPEC.dir-sep)),
nqp::substr($kid.absolute, 0, nqp::chars($_))),
$kid, # kid appears to be kid-proper; return it. Otherwise fail
fail X::IO::NotAChild.new:
:path($res-self.absolute), :child($kid.absolute))))
}

method concat-with (IO::Path:D: Str() \what) {
self.bless: :path($!SPEC.join: '', $!path, what), :$!SPEC, :$!CWD;
}
Expand Down

0 comments on commit 1887114

Please sign in to comment.