Skip to content

Commit

Permalink
Add compile-time validation of literal paths containing ".." (#329)
Browse files Browse the repository at this point in the history
Addresses #327 

This PR adds additional validation of literal path segments containing
`..`.

It throws compiletime error, suggesting canonical form of a path, when
literal path is not canonical.

Eg. "../foo/../bar"   suggests  "../bar"

"foo/.." suggests removing literal

---------

Co-authored-by: Li Haoyi <[email protected]>
  • Loading branch information
pawelsadlo and lihaoyi authored Nov 12, 2024
1 parent 1ddbb25 commit 8d4bf10
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 7 deletions.
11 changes: 9 additions & 2 deletions os/src/Path.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ object PathChunk extends PathChunkMacros {
val splitted = strNoTrailingSeps.split('/')
splitted ++ Array.fill(trailingSeparatorsCount)("")
}

private def reduceUps(in: Array[String]): List[String] =
in.foldLeft(List.empty[String]) { case (acc, x) =>
acc match {
case h :: t if h == ".." => x :: acc
case h :: t if x == ".." => t
case _ => x :: acc
}
}.reverse
private[os] def segmentsFromStringLiteralValidation(literal: String): Array[String] = {
val stringSegments = segmentsFromString(literal)
val validSegmnts = validLiteralSegments(stringSegments)
val validSegmnts = reduceUps(validLiteralSegments(stringSegments))
val sanitizedLiteral = validSegmnts.mkString("/")
if (validSegmnts.isEmpty) throw InvalidSegment(
literal,
Expand Down
7 changes: 4 additions & 3 deletions os/test/src-jvm/ExpectyIntegration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ object ExpectyIntegration extends TestSuite {
}
test("literals with [..]") {
expect(rel / "src" / ".." == rel / "src" / os.up)
expect(root / "src/.." == root / "src" / os.up)
expect(root / "src" / ".." == root / "src" / os.up)
expect(root / "hello" / ".." / "world" == root / "hello" / os.up / "world")
expect(root / "hello" / "../world" == root / "hello" / os.up / "world")
expect(root / "hello/../world" == root / "hello" / os.up / "world")
}
test("from issue") {
expect(Seq(os.pwd / "foo") == Seq(os.pwd / "foo"))
val path = os.Path("/") / "tmp" / "foo"
expect(path.startsWith(os.Path("/") / "tmp"))
}
test("multiple args") {
expect(rel / "src" / ".." == rel / "src" / os.up, root / "src/.." == root / "src" / os.up)
expect(
rel / "src" / ".." == rel / "src" / os.up,
root / "src" / "../foo" == root / "src" / os.up / "foo"
)
}
}
}
Expand Down
42 changes: 40 additions & 2 deletions os/test/src/PathTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,52 @@ object PathTests extends TestSuite {
test("literals with [..]") {

assert(rel / "src" / ".." == rel / "src" / os.up)
assert(root / "src/.." == root / "src" / os.up)
assert(root / "src" / ".." == root / "src" / os.up)
assert(root / "hello" / ".." / "world" == root / "hello" / os.up / "world")
assert(root / "hello" / "../world" == root / "hello" / os.up / "world")
assert(root / "hello/../world" == root / "hello" / os.up / "world")
}

test("Compile errors") {

compileError("""root / "src/../foo"""").check("", nonCanonicalLiteral("src/../foo", "foo"))
compileError("""root / "hello/../world"""").check(
"",
nonCanonicalLiteral("hello/../world", "world")
)
compileError("""root / "src/../foo/bar"""").check(
"",
nonCanonicalLiteral("src/../foo/bar", "foo/bar")
)
compileError("""root / "src/../foo/bar/.."""").check(
"",
nonCanonicalLiteral("src/../foo/bar/..", "foo")
)
compileError("""root / "src/../foo/../bar/."""").check(
"",
nonCanonicalLiteral("src/../foo/../bar/.", "bar")
)
compileError("""root / "src/foo/./.."""").check(
"",
nonCanonicalLiteral("src/foo/./..", "src")
)
compileError("""root / "src/foo//./.."""").check(
"",
nonCanonicalLiteral("src/foo//./..", "src")
)

compileError("""root / "src/.."""").check("", removeLiteralErr("src/.."))
compileError("""root / "src/../foo/.."""").check("", removeLiteralErr("src/../foo/.."))
compileError("""root / "src/foo/../.."""").check("", removeLiteralErr("src/foo/../.."))
compileError("""root / "src/foo/./../.."""").check("", removeLiteralErr("src/foo/./../.."))
compileError("""root / "src/./foo/./../.."""").check(
"",
removeLiteralErr("src/./foo/./../..")
)
compileError("""root / "src///foo/./../.."""").check(
"",
removeLiteralErr("src///foo/./../..")
)

compileError("""root / "/" """).check("", removeLiteralErr("/"))
compileError("""root / "/ " """).check("", nonCanonicalLiteral("/ ", " "))
compileError("""root / " /" """).check("", nonCanonicalLiteral(" /", " "))
Expand Down

0 comments on commit 8d4bf10

Please sign in to comment.