Skip to content

Commit a78bd00

Browse files
authored
(154976578) String.pathExtension should allow "..name" file names but not ".." (swiftlang#1410)
1 parent db4bab8 commit a78bd00

File tree

2 files changed

+24
-6
lines changed

2 files changed

+24
-6
lines changed

Sources/FoundationEssentials/String/String+Path.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,19 @@ extension String {
224224
}
225225

226226
internal var pathExtension: String {
227-
let lastComponent = lastPathComponent.utf8
228-
guard lastComponent.last != ._dot,
229-
!lastComponent.starts(with: [._dot, ._dot]),
230-
let lastDot = lastComponent.lastIndex(of: ._dot),
231-
lastDot != lastComponent.startIndex else {
227+
let utf8Component = lastPathComponent.utf8
228+
guard utf8Component.last != ._dot,
229+
let lastDot = utf8Component.lastIndex(of: ._dot),
230+
// Don't treat a hidden file name as an extension
231+
lastDot != utf8Component.startIndex else {
232232
return ""
233233
}
234-
let result = String(lastPathComponent[lastComponent.index(after: lastDot)...])
234+
let utf8FileName = utf8Component[..<lastDot]
235+
// Guard against "." and ".." file names
236+
if (utf8FileName.count == 1 || utf8FileName.count == 2) && utf8FileName.allSatisfy({ $0 == ._dot }) {
237+
return ""
238+
}
239+
let result = String(lastPathComponent[utf8Component.index(after: lastDot)...])
235240
guard validatePathExtension(result) else {
236241
return ""
237242
}

Tests/FoundationEssentialsTests/StringTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,16 +854,29 @@ private struct StringTests {
854854
#expect("/foo/bar/.zip".deletingPathExtension() == "/foo/bar/.zip")
855855
#expect("..".deletingPathExtension() == "..")
856856
#expect("..zip".deletingPathExtension() == "..zip")
857+
#expect("/..".deletingPathExtension() == "/..")
858+
#expect("/..zip".deletingPathExtension() == "/..zip")
857859
#expect("/foo/bar/..zip".deletingPathExtension() == "/foo/bar/..zip")
858860
#expect("/foo/bar/baz..zip".deletingPathExtension() == "/foo/bar/baz.")
859861
#expect("...".deletingPathExtension() == "...")
860862
#expect("...zip".deletingPathExtension() == "...zip")
863+
#expect("/...".deletingPathExtension() == "/...")
864+
#expect("/...zip".deletingPathExtension() == "/...zip")
861865
#expect("/foo/bar/...zip".deletingPathExtension() == "/foo/bar/...zip")
862866
#expect("/foo/bar/baz...zip".deletingPathExtension() == "/foo/bar/baz..")
863867
#expect("/foo.bar/bar.baz/baz.zip".deletingPathExtension() == "/foo.bar/bar.baz/baz")
864868
#expect("/.././.././a.zip".deletingPathExtension() == "/.././.././a")
865869
#expect("/.././.././.".deletingPathExtension() == "/.././.././.")
866870

871+
// File names starting with "." or ".." are OK
872+
// as long as they aren't exactly "." or ".."
873+
#expect("..name.txt".deletingPathExtension() == "..name")
874+
#expect("/..name.txt".deletingPathExtension() == "/..name")
875+
#expect("....txt".deletingPathExtension() == "...")
876+
#expect("/....txt".deletingPathExtension() == "/...")
877+
#expect(".name.txt".deletingPathExtension() == ".name")
878+
#expect("/.name.txt".deletingPathExtension() == "/.name")
879+
867880
#expect("path.foo".deletingPathExtension() == "path")
868881
#expect("path.foo.zip".deletingPathExtension() == "path.foo")
869882
#expect("/path.foo".deletingPathExtension() == "/path")

0 commit comments

Comments
 (0)