From 7c256285cdd604a254a8fb8d7bf02e3ab78e2835 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Sun, 6 Oct 2024 14:46:52 -0600 Subject: [PATCH] (137287143) URL path extension APIs should strip trailing slashes --- .../String/String+Path.swift | 17 ++++++++++++-- .../StringTests.swift | 22 +++++++++++++++++++ .../FoundationEssentialsTests/URLTests.swift | 21 ++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Sources/FoundationEssentials/String/String+Path.swift b/Sources/FoundationEssentials/String/String+Path.swift index 17f033857..f1cb26d28 100644 --- a/Sources/FoundationEssentials/String/String+Path.swift +++ b/Sources/FoundationEssentials/String/String+Path.swift @@ -186,7 +186,11 @@ extension String { guard let lastDot = utf8.lastIndex(of: ._dot) else { return self } - return String(self[.. Bool { @@ -206,7 +210,16 @@ extension String { guard validatePathExtension(pathExtension) else { return self } - return self + ".\(pathExtension)" + var result = self._droppingTrailingSlashes + guard result != "/" else { + // Path was all slashes + return self + ".\(pathExtension)" + } + result += ".\(pathExtension)" + if utf8.last == ._slash { + result += "/" + } + return result } internal var pathExtension: String { diff --git a/Tests/FoundationEssentialsTests/StringTests.swift b/Tests/FoundationEssentialsTests/StringTests.swift index 71b70417a..54d9e7bb9 100644 --- a/Tests/FoundationEssentialsTests/StringTests.swift +++ b/Tests/FoundationEssentialsTests/StringTests.swift @@ -813,6 +813,19 @@ final class StringTests : XCTestCase { } } + func testAppendingPathExtension() { + XCTAssertEqual("".appendingPathExtension("foo"), ".foo") + XCTAssertEqual("/".appendingPathExtension("foo"), "/.foo") + XCTAssertEqual("//".appendingPathExtension("foo"), "//.foo") + XCTAssertEqual("/path".appendingPathExtension("foo"), "/path.foo") + XCTAssertEqual("/path.zip".appendingPathExtension("foo"), "/path.zip.foo") + XCTAssertEqual("/path/".appendingPathExtension("foo"), "/path.foo/") + XCTAssertEqual("/path//".appendingPathExtension("foo"), "/path.foo/") + XCTAssertEqual("path".appendingPathExtension("foo"), "path.foo") + XCTAssertEqual("path/".appendingPathExtension("foo"), "path.foo/") + XCTAssertEqual("path//".appendingPathExtension("foo"), "path.foo/") + } + func testDeletingPathExtenstion() { XCTAssertEqual("".deletingPathExtension(), "") XCTAssertEqual("/".deletingPathExtension(), "/") @@ -835,6 +848,15 @@ final class StringTests : XCTestCase { XCTAssertEqual("/foo.bar/bar.baz/baz.zip".deletingPathExtension(), "/foo.bar/bar.baz/baz") XCTAssertEqual("/.././.././a.zip".deletingPathExtension(), "/.././.././a") XCTAssertEqual("/.././.././.".deletingPathExtension(), "/.././.././.") + + XCTAssertEqual("path.foo".deletingPathExtension(), "path") + XCTAssertEqual("path.foo.zip".deletingPathExtension(), "path.foo") + XCTAssertEqual("/path.foo".deletingPathExtension(), "/path") + XCTAssertEqual("/path.foo.zip".deletingPathExtension(), "/path.foo") + XCTAssertEqual("path.foo/".deletingPathExtension(), "path/") + XCTAssertEqual("path.foo//".deletingPathExtension(), "path/") + XCTAssertEqual("/path.foo/".deletingPathExtension(), "/path/") + XCTAssertEqual("/path.foo//".deletingPathExtension(), "/path/") } func testPathComponents() { diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index 9e4c388f6..d8c0c82e3 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -647,6 +647,27 @@ final class URLTests : XCTestCase { XCTAssertEqual(url.path().utf8.last, ._slash) } + func testURLPathExtensions() throws { + var url = URL(filePath: "/path", directoryHint: .notDirectory) + url.appendPathExtension("foo") + XCTAssertEqual(url.path(), "/path.foo") + url.deletePathExtension() + XCTAssertEqual(url.path(), "/path") + + url = URL(filePath: "/path", directoryHint: .isDirectory) + url.appendPathExtension("foo") + XCTAssertEqual(url.path(), "/path.foo/") + url.deletePathExtension() + XCTAssertEqual(url.path(), "/path/") + + url = URL(filePath: "/path/", directoryHint: .inferFromPath) + url.appendPathExtension("foo") + XCTAssertEqual(url.path(), "/path.foo/") + url.append(path: "/////") + url.deletePathExtension() + XCTAssertEqual(url.path(), "/path/") + } + func testURLComponentsPercentEncodedUnencodedProperties() throws { var comp = URLComponents()