diff --git a/Sources/SMBClient/Misc/Pathname.swift b/Sources/SMBClient/Misc/Pathname.swift index cbb53c5..947e68b 100644 --- a/Sources/SMBClient/Misc/Pathname.swift +++ b/Sources/SMBClient/Misc/Pathname.swift @@ -10,7 +10,9 @@ enum Pathname { } } - static func escape(_ path: String) -> String { - path.replacingOccurrences(of: "/", with: #"\"#) + static func normalize(_ path: String) -> String { + path + .trimmingCharacters(in: CharacterSet(charactersIn: #"/\"#)) + .replacingOccurrences(of: "/", with: #"\"#) } } diff --git a/Sources/SMBClient/SMBClient.swift b/Sources/SMBClient/SMBClient.swift index 16220df..2b2cf8f 100644 --- a/Sources/SMBClient/SMBClient.swift +++ b/Sources/SMBClient/SMBClient.swift @@ -74,50 +74,50 @@ public class SMBClient { } public func listDirectory(path: String, pattern: String = "*") async throws -> [File] { - let files = try await session.queryDirectory(path: Pathname.escape(path), pattern: pattern) + let files = try await session.queryDirectory(path: Pathname.normalize(path), pattern: pattern) return files.map { File(fileInfo: $0) } } public func createDirectory(path: String) async throws { - try await session.createDirectory(path: Pathname.escape(path)) + try await session.createDirectory(path: Pathname.normalize(path)) } public func rename(from: String, to: String) async throws { - try await move(from: Pathname.escape(from), to: Pathname.escape(to)) + try await move(from: Pathname.normalize(from), to: Pathname.normalize(to)) } public func move(from: String, to: String) async throws { - try await session.move(from: Pathname.escape(from), to: Pathname.escape(to)) + try await session.move(from: Pathname.normalize(from), to: Pathname.normalize(to)) } public func deleteDirectory(path: String) async throws { - try await session.deleteDirectory(path: Pathname.escape(path)) + try await session.deleteDirectory(path: Pathname.normalize(path)) } public func deleteFile(path: String) async throws { - try await session.deleteFile(path: Pathname.escape(path)) + try await session.deleteFile(path: Pathname.normalize(path)) } public func fileStat(path: String) async throws -> FileStat { - let response = try await session.fileStat(path: Pathname.escape(path)) + let response = try await session.fileStat(path: Pathname.normalize(path)) return FileStat(response) } public func existFile(path: String) async throws -> Bool { - try await session.existFile(path: Pathname.escape(path)) + try await session.existFile(path: Pathname.normalize(path)) } public func existDirectory(path: String) async throws -> Bool { - try await session.existDirectory(path: Pathname.escape(path)) + try await session.existDirectory(path: Pathname.normalize(path)) } public func fileInfo(path: String) async throws -> FileAllInformation { - let response = try await session.queryInfo(path: Pathname.escape(path)) + let response = try await session.queryInfo(path: Pathname.normalize(path)) return FileAllInformation(data: response.buffer) } public func download(path: String) async throws -> Data { - let fileReader = fileReader(path: Pathname.escape(path)) + let fileReader = fileReader(path: Pathname.normalize(path)) let data = try await fileReader.download() try await fileReader.close() @@ -126,11 +126,11 @@ public class SMBClient { } public func upload(content: Data, path: String) async throws { - try await upload(content: content, path: Pathname.escape(path), progressHandler: { _ in }) + try await upload(content: content, path: Pathname.normalize(path), progressHandler: { _ in }) } public func upload(content: Data, path: String, progressHandler: (_ progress: Double) -> Void) async throws { - let fileWriter = fileWriter(path: Pathname.escape(path)) + let fileWriter = fileWriter(path: Pathname.normalize(path)) try await fileWriter.upload(data: content, progressHandler: progressHandler) try await fileWriter.close() @@ -141,7 +141,7 @@ public class SMBClient { } public func upload(fileHandle: FileHandle, path: String, progressHandler: (_ progress: Double) -> Void) async throws { - let fileWriter = fileWriter(path: Pathname.escape(path)) + let fileWriter = fileWriter(path: Pathname.normalize(path)) try await fileWriter.upload(fileHandle: fileHandle, progressHandler: progressHandler) try await fileWriter.close() @@ -156,18 +156,18 @@ public class SMBClient { remotePath path: String, progressHandler: (_ completedFiles: Int, _ fileBeingTransferred: URL, _ bytesSent: Int64) -> Void ) async throws { - let fileWriter = fileWriter(path: Pathname.escape(path)) + let fileWriter = fileWriter(path: Pathname.normalize(path)) try await fileWriter.upload(localPath: localPath, progressHandler: progressHandler) try await fileWriter.close() } public func fileReader(path: String) -> FileReader { - FileReader(session: session, path: Pathname.escape(path)) + FileReader(session: session, path: Pathname.normalize(path)) } public func fileWriter(path: String) -> FileWriter { - FileWriter(session: session, path: Pathname.escape(path)) + FileWriter(session: session, path: Pathname.normalize(path)) } public func keepAlive() async throws -> Echo.Response { diff --git a/Tests/SMBClientTests/SMBClientTests.swift b/Tests/SMBClientTests/SMBClientTests.swift index f977413..5656f33 100644 --- a/Tests/SMBClientTests/SMBClientTests.swift +++ b/Tests/SMBClientTests/SMBClientTests.swift @@ -229,6 +229,92 @@ final class SMBClientTests: XCTestCase { try await client.logoff() } + func testListDirectory06() async throws { + let user = bob + let client = SMBClient(host: "localhost", port: 4445) + try await client.login(username: user.username, password: user.password) + + let share = user.share + let shareDirectory = user.sharePath + try await client.connectShare(share) + + func listDirectory(share: String, shareDirectory: String, path: String) async throws { + let files = try await client.listDirectory(path: path) + .filter { $0.name != "." && $0.name != ".." } + .sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending } + + let fileManager = FileManager() + let root = fixtureURL.appending(component: "\(shareDirectory)/\(path)") + let testFiles = try fileManager.contentsOfDirectory(atPath: root.path(percentEncoded: false)) + .filter { $0 != ".DS_Store" } + .sorted { $0.localizedStandardCompare($1) == .orderedAscending } + + for (actualFile, expectedFile) in zip(files, testFiles) { + XCTAssertEqual(actualFile.name, expectedFile) + + var isDirectory: ObjCBool = false + fileManager.fileExists(atPath: root.appending(component: expectedFile).path(percentEncoded: false), isDirectory: &isDirectory) + XCTAssertEqual(isDirectory.boolValue, actualFile.isDirectory) + + if actualFile.isDirectory { + if path.isEmpty { + try await listDirectory(share: share, shareDirectory: shareDirectory, path: actualFile.name) + } else { + try await listDirectory(share: share, shareDirectory: shareDirectory, path: "\(path)/\(actualFile.name)") + } + } + } + } + + try await listDirectory(share: share, shareDirectory: shareDirectory, path: "/test_files") + + try await client.treeDisconnect() + try await client.logoff() + } + + func testListDirectory07() async throws { + let user = bob + let client = SMBClient(host: "localhost", port: 4445) + try await client.login(username: user.username, password: user.password) + + let share = user.share + let shareDirectory = user.sharePath + try await client.connectShare(share) + + func listDirectory(share: String, shareDirectory: String, path: String) async throws { + let files = try await client.listDirectory(path: path) + .filter { $0.name != "." && $0.name != ".." } + .sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending } + + let fileManager = FileManager() + let root = fixtureURL.appending(component: "\(shareDirectory)/\(path)") + let testFiles = try fileManager.contentsOfDirectory(atPath: root.path(percentEncoded: false)) + .filter { $0 != ".DS_Store" } + .sorted { $0.localizedStandardCompare($1) == .orderedAscending } + + for (actualFile, expectedFile) in zip(files, testFiles) { + XCTAssertEqual(actualFile.name, expectedFile) + + var isDirectory: ObjCBool = false + fileManager.fileExists(atPath: root.appending(component: expectedFile).path(percentEncoded: false), isDirectory: &isDirectory) + XCTAssertEqual(isDirectory.boolValue, actualFile.isDirectory) + + if actualFile.isDirectory { + if path.isEmpty { + try await listDirectory(share: share, shareDirectory: shareDirectory, path: actualFile.name) + } else { + try await listDirectory(share: share, shareDirectory: shareDirectory, path: "\(path)/\(actualFile.name)") + } + } + } + } + + try await listDirectory(share: share, shareDirectory: shareDirectory, path: "/") + + try await client.treeDisconnect() + try await client.logoff() + } + func testCreateDirectory() async throws { let user = alice let client = SMBClient(host: "localhost", port: 4445)