Skip to content

Commit 8d4bf10

Browse files
pawelsadlolihaoyi
andauthored
Add compile-time validation of literal paths containing ".." (#329)
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]>
1 parent 1ddbb25 commit 8d4bf10

File tree

3 files changed

+53
-7
lines changed

3 files changed

+53
-7
lines changed

os/src/Path.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,17 @@ object PathChunk extends PathChunkMacros {
2626
val splitted = strNoTrailingSeps.split('/')
2727
splitted ++ Array.fill(trailingSeparatorsCount)("")
2828
}
29-
29+
private def reduceUps(in: Array[String]): List[String] =
30+
in.foldLeft(List.empty[String]) { case (acc, x) =>
31+
acc match {
32+
case h :: t if h == ".." => x :: acc
33+
case h :: t if x == ".." => t
34+
case _ => x :: acc
35+
}
36+
}.reverse
3037
private[os] def segmentsFromStringLiteralValidation(literal: String): Array[String] = {
3138
val stringSegments = segmentsFromString(literal)
32-
val validSegmnts = validLiteralSegments(stringSegments)
39+
val validSegmnts = reduceUps(validLiteralSegments(stringSegments))
3340
val sanitizedLiteral = validSegmnts.mkString("/")
3441
if (validSegmnts.isEmpty) throw InvalidSegment(
3542
literal,

os/test/src-jvm/ExpectyIntegration.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,20 @@ object ExpectyIntegration extends TestSuite {
1414
}
1515
test("literals with [..]") {
1616
expect(rel / "src" / ".." == rel / "src" / os.up)
17-
expect(root / "src/.." == root / "src" / os.up)
1817
expect(root / "src" / ".." == root / "src" / os.up)
1918
expect(root / "hello" / ".." / "world" == root / "hello" / os.up / "world")
2019
expect(root / "hello" / "../world" == root / "hello" / os.up / "world")
21-
expect(root / "hello/../world" == root / "hello" / os.up / "world")
2220
}
2321
test("from issue") {
2422
expect(Seq(os.pwd / "foo") == Seq(os.pwd / "foo"))
2523
val path = os.Path("/") / "tmp" / "foo"
2624
expect(path.startsWith(os.Path("/") / "tmp"))
2725
}
2826
test("multiple args") {
29-
expect(rel / "src" / ".." == rel / "src" / os.up, root / "src/.." == root / "src" / os.up)
27+
expect(
28+
rel / "src" / ".." == rel / "src" / os.up,
29+
root / "src" / "../foo" == root / "src" / os.up / "foo"
30+
)
3031
}
3132
}
3233
}

os/test/src/PathTests.scala

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,52 @@ object PathTests extends TestSuite {
2323
test("literals with [..]") {
2424

2525
assert(rel / "src" / ".." == rel / "src" / os.up)
26-
assert(root / "src/.." == root / "src" / os.up)
2726
assert(root / "src" / ".." == root / "src" / os.up)
2827
assert(root / "hello" / ".." / "world" == root / "hello" / os.up / "world")
2928
assert(root / "hello" / "../world" == root / "hello" / os.up / "world")
30-
assert(root / "hello/../world" == root / "hello" / os.up / "world")
3129
}
3230

3331
test("Compile errors") {
32+
33+
compileError("""root / "src/../foo"""").check("", nonCanonicalLiteral("src/../foo", "foo"))
34+
compileError("""root / "hello/../world"""").check(
35+
"",
36+
nonCanonicalLiteral("hello/../world", "world")
37+
)
38+
compileError("""root / "src/../foo/bar"""").check(
39+
"",
40+
nonCanonicalLiteral("src/../foo/bar", "foo/bar")
41+
)
42+
compileError("""root / "src/../foo/bar/.."""").check(
43+
"",
44+
nonCanonicalLiteral("src/../foo/bar/..", "foo")
45+
)
46+
compileError("""root / "src/../foo/../bar/."""").check(
47+
"",
48+
nonCanonicalLiteral("src/../foo/../bar/.", "bar")
49+
)
50+
compileError("""root / "src/foo/./.."""").check(
51+
"",
52+
nonCanonicalLiteral("src/foo/./..", "src")
53+
)
54+
compileError("""root / "src/foo//./.."""").check(
55+
"",
56+
nonCanonicalLiteral("src/foo//./..", "src")
57+
)
58+
59+
compileError("""root / "src/.."""").check("", removeLiteralErr("src/.."))
60+
compileError("""root / "src/../foo/.."""").check("", removeLiteralErr("src/../foo/.."))
61+
compileError("""root / "src/foo/../.."""").check("", removeLiteralErr("src/foo/../.."))
62+
compileError("""root / "src/foo/./../.."""").check("", removeLiteralErr("src/foo/./../.."))
63+
compileError("""root / "src/./foo/./../.."""").check(
64+
"",
65+
removeLiteralErr("src/./foo/./../..")
66+
)
67+
compileError("""root / "src///foo/./../.."""").check(
68+
"",
69+
removeLiteralErr("src///foo/./../..")
70+
)
71+
3472
compileError("""root / "/" """).check("", removeLiteralErr("/"))
3573
compileError("""root / "/ " """).check("", nonCanonicalLiteral("/ ", " "))
3674
compileError("""root / " /" """).check("", nonCanonicalLiteral(" /", " "))

0 commit comments

Comments
 (0)