Skip to content

Commit 9097d93

Browse files
Add shared library for filepath normalization
1 parent 1297acf commit 9097d93

File tree

4 files changed

+102
-0
lines changed

4 files changed

+102
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
| | . |
2+
| ./a/b/c/../d | a/b/d |
3+
| / | / |
4+
| /a/b/../c | /a/c |
5+
| /a/b/c/../../d/ | /a/d |
6+
| a/.. | . |
7+
| a/b/../c | a/c |
8+
| a/b//////c/./d/../e//d// | a/b/c/e/d |
9+
| a/b/c | a/b/c |
10+
| a/b/c/../../../../d/e/../f | ../d/f |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import codeql.util.FilePath
2+
3+
class FilepathTest extends NormalizableFilepath {
4+
FilepathTest() {
5+
this =
6+
[
7+
"a/b/c", "a/b/../c", "a/..", "/a/b/../c", "a/b/c/../../../../d/e/../f", "", "/",
8+
"./a/b/c/../d", "a/b//////c/./d/../e//d//", "/a/b/c/../../d/"
9+
]
10+
}
11+
}
12+
13+
from FilepathTest s
14+
select s, s.getNormalizedPath()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
category: feature
2+
---
3+
* Added `FilePath` API for normalizing filepaths.
4+
```

shared/util/codeql/util/FilePath.qll

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/** Provides a utility for normalizing filepaths. */
2+
3+
/**
4+
* A filepath that should be normalized.
5+
* Extend to provide additional strings that should be normalized as filepaths.
6+
*/
7+
abstract class NormalizableFilepath extends string {
8+
bindingset[this]
9+
NormalizableFilepath() { any() }
10+
11+
private string getComponent(int i) { result = this.splitAt("/", i) }
12+
13+
private int getNumComponents() { result = strictcount(int i | exists(this.getComponent(i))) }
14+
15+
/** count .. segments in prefix in normalization from index i */
16+
private int dotdotCountFrom(int i) {
17+
result = 0 and i = this.getNumComponents()
18+
or
19+
exists(string c | c = this.getComponent(i) |
20+
if c = ""
21+
then result = this.dotdotCountFrom(i + 1)
22+
else
23+
if c = "."
24+
then result = this.dotdotCountFrom(i + 1)
25+
else
26+
if c = ".."
27+
then result = this.dotdotCountFrom(i + 1) + 1
28+
else result = (this.dotdotCountFrom(i + 1) - 1).maximum(0)
29+
)
30+
}
31+
32+
/** count non-.. segments in postfix in normalization from index 0 to i-1 */
33+
private int segmentCountUntil(int i) {
34+
result = 0 and i = 0
35+
or
36+
exists(string c | c = this.getComponent(i - 1) |
37+
if c = ""
38+
then result = this.segmentCountUntil(i - 1)
39+
else
40+
if c = "."
41+
then result = this.segmentCountUntil(i - 1)
42+
else
43+
if c = ".."
44+
then result = (this.segmentCountUntil(i - 1) - 1).maximum(0)
45+
else result = this.segmentCountUntil(i - 1) + 1
46+
)
47+
}
48+
49+
private string part(int i) {
50+
result = this.getComponent(i) and
51+
result != "." and
52+
if result = ""
53+
then i = 0
54+
else (
55+
result != ".." and
56+
0 = this.dotdotCountFrom(i + 1)
57+
or
58+
result = ".." and
59+
0 = this.segmentCountUntil(i)
60+
)
61+
}
62+
63+
/** Gets the normalized filepath for this string; traversing `/../` paths. */
64+
string getNormalizedPath() {
65+
exists(string norm | norm = concat(string s, int i | s = this.part(i) | s, "/" order by i) |
66+
if norm != ""
67+
then result = norm
68+
else
69+
if this.matches("/%")
70+
then result = "/"
71+
else result = "."
72+
)
73+
}
74+
}

0 commit comments

Comments
 (0)