From 17f9c554087d620970dbced3d502cbdb5f5714d4 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 18 Oct 2025 14:58:09 +1300 Subject: [PATCH 1/2] Better relative path simplification. --- lib/utopia/path.rb | 33 ++++++++++++++++++-------- releases.md | 1 + test/utopia/path.rb | 56 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/lib/utopia/path.rb b/lib/utopia/path.rb index b6422cf..af3a1de 100644 --- a/lib/utopia/path.rb +++ b/lib/utopia/path.rb @@ -215,19 +215,34 @@ def -(other) end def simplify - result = absolute? ? [""] : [] + components = [] - @components.each do |bit| - if bit == ".." - result.pop - elsif bit != "." && bit != "" - result << bit - end + index = 0 + + if @components[0] == "" + components << "" + index += 1 end - result << "" if directory? + while index < @components.size + bit = @components[index] + if bit == "." + # No-op (ignore current directory) + elsif bit == "" && index != @components.size - 1 + # No-op (ignore multiple slashes) + elsif bit == ".." && components.last && components.last != ".." + if components.last != "" + # We can go up one level: + components.pop + end + else + components << bit + end + + index += 1 + end - return self.class.new(result) + return self.class.new(components) end # Returns the first path component. diff --git a/releases.md b/releases.md index 94236dd..47890ec 100644 --- a/releases.md +++ b/releases.md @@ -3,6 +3,7 @@ ## Unreleased - Add agent context. + - Better simplification of relative paths, e.g. `../../foo` is not modified to `foo`. ## v2.30.1 diff --git a/test/utopia/path.rb b/test/utopia/path.rb index d7bdbfd..920da73 100755 --- a/test/utopia/path.rb +++ b/test/utopia/path.rb @@ -195,6 +195,7 @@ expect(directory.to_directory).to be == directory end end + it "should start with the given path" do path = Utopia::Path["/a/b/c/d/e"] @@ -263,4 +264,59 @@ expect((output + short).simplify).to be == input end + + with "#simplify" do + it "doesn't remove leading .. from relative paths" do + path = Utopia::Path["../foo/bar"] + simplified = path.simplify + + expect(simplified.components).to be == ["..", "foo", "bar"] + end + + it "removes redundant .. from absolute paths" do + path = Utopia::Path["/foo/../../bar"] + simplified = path.simplify + + expect(simplified.components).to be == ["", "bar"] + end + + it "preserves excess .. for purely relative paths" do + # ../../foo cannot be reduced without a base; keep leading .. components + path = Utopia::Path["../../foo"] + simplified = path.simplify + + expect(simplified.components).to be == ["..", "..", "foo"] + end + + it "reduces relative paths only as far as components allow" do + # foo/../../bar -> ../bar (pop foo for first .., then keep remaining ..) + path = Utopia::Path["foo/../../bar"] + simplified = path.simplify + + expect(simplified.components).to be == ["..", "bar"] + end + + it "does not traverse above root for absolute paths" do + # /a/b/../../../c -> /c (ignore .. beyond root) + path = Utopia::Path["/a/b/../../../c"] + simplified = path.simplify + + expect(simplified.components).to be == ["", "c"] + end + + it "ignores leading .. at absolute root" do + # /../../c -> /c + path = Utopia::Path["/../../c"] + simplified = path.simplify + + expect(simplified.components).to be == ["", "c"] + end + + it "preserves trailing slash for directories after simplify" do + path = Utopia::Path["/a/./b/../c/"] + simplified = path.simplify + + expect(simplified.components).to be == ["", "a", "c", ""] + end + end end From 76d0329ef3831406106b965eaef404faf927bbcb Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 18 Oct 2025 15:45:21 +1300 Subject: [PATCH 2/2] RuboCop. --- test/utopia/path.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/utopia/path.rb b/test/utopia/path.rb index 920da73..417f2b8 100755 --- a/test/utopia/path.rb +++ b/test/utopia/path.rb @@ -279,7 +279,7 @@ expect(simplified.components).to be == ["", "bar"] end - + it "preserves excess .. for purely relative paths" do # ../../foo cannot be reduced without a base; keep leading .. components path = Utopia::Path["../../foo"] @@ -287,7 +287,7 @@ expect(simplified.components).to be == ["..", "..", "foo"] end - + it "reduces relative paths only as far as components allow" do # foo/../../bar -> ../bar (pop foo for first .., then keep remaining ..) path = Utopia::Path["foo/../../bar"] @@ -295,7 +295,7 @@ expect(simplified.components).to be == ["..", "bar"] end - + it "does not traverse above root for absolute paths" do # /a/b/../../../c -> /c (ignore .. beyond root) path = Utopia::Path["/a/b/../../../c"] @@ -303,7 +303,7 @@ expect(simplified.components).to be == ["", "c"] end - + it "ignores leading .. at absolute root" do # /../../c -> /c path = Utopia::Path["/../../c"] @@ -311,7 +311,7 @@ expect(simplified.components).to be == ["", "c"] end - + it "preserves trailing slash for directories after simplify" do path = Utopia::Path["/a/./b/../c/"] simplified = path.simplify