From 4dbb0adb189ebe4415f12828d8a75816782862f7 Mon Sep 17 00:00:00 2001 From: Amir Abbas Mousavian Date: Thu, 30 Mar 2017 13:16:03 +0430 Subject: [PATCH] FTP better error handling --- Sources/FPSStreamTask.swift | 6 +- Sources/FTPFileProvider.swift | 7 ++- Sources/FTPHelper.swift | 100 +++++++++++++++++++++++++++++----- 3 files changed, 95 insertions(+), 18 deletions(-) diff --git a/Sources/FPSStreamTask.swift b/Sources/FPSStreamTask.swift index 626d5ec..7269dfc 100644 --- a/Sources/FPSStreamTask.swift +++ b/Sources/FPSStreamTask.swift @@ -551,7 +551,7 @@ internal protocol FPSStreamDelegate : URLSessionTaskDelegate { @objc optional func urlSession(_ session: URLSession, streamTask: FPSStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream) } -private let ports: [String: Int] = ["http": 80, "https": 443, "smb": 445,"ftp": 21,"ftps": 22, "sftp": 2121, +private let ports: [String: Int] = ["http": 80, "https": 443, "smb": 445,"ftp": 21, "telnet": 23, "pop": 110, "smtp": 25, "imap": 143] -private let securePorts: [String: Int] = ["https": 443, "smb": 445, "ftps": 990, "sftp": 2121, - "telnet": 992, "pop": 995, "smtp": 465, "imap": 993] +private let securePorts: [String: Int] = ["ssh": 22, "https": 443, "smb": 445, "smtp": 465, + "ftps": 990,"telnet": 992, "imap": 993, "pop": 995] diff --git a/Sources/FTPFileProvider.swift b/Sources/FTPFileProvider.swift index 2a2cb3a..1184519 100644 --- a/Sources/FTPFileProvider.swift +++ b/Sources/FTPFileProvider.swift @@ -199,7 +199,7 @@ open class FTPFileProvider: FileProviderBasicRemote { return } - guard let response = response, response.hasPrefix("250") else { + guard let response = response, response.hasPrefix("250") || (response.hasPrefix("50") && rfc3659enabled) else { let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil) self.dispatch_queue.async { completionHandler(nil, error) @@ -207,6 +207,10 @@ open class FTPFileProvider: FileProviderBasicRemote { return } + if response.hasPrefix("500") { + self.attributesOfItem(path: path, rfc3659enabled: false, completionHandler: completionHandler) + } + let lines = response.components(separatedBy: "\n").flatMap { $0.isEmpty ? nil : $0.trimmingCharacters(in: .whitespacesAndNewlines) } guard lines.count > 2 else { let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil) @@ -224,7 +228,6 @@ open class FTPFileProvider: FileProviderBasicRemote { } open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) { - // TODO: implement SITE QUOTA extension dispatch_queue.async { completionHandler(-1, 0) } diff --git a/Sources/FTPHelper.swift b/Sources/FTPHelper.swift index 4b32303..9934545 100644 --- a/Sources/FTPHelper.swift +++ b/Sources/FTPHelper.swift @@ -88,7 +88,10 @@ extension FTPFileProvider { } guard response.hasPrefix("22") else { - let error = NSError(domain: URLError.errorDomain, code: URLError.cannotConnectToHost.rawValue, userInfo: nil) + let spaceIndex = response.characters.index(of: " ") ?? response.startIndex + let code = Int(response.substring(to: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1 + let description = response.substring(from: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines) + let error = FileProviderFTPError(code: code, path: "", errorDescription: description) completionHandler(error) return } @@ -108,8 +111,8 @@ extension FTPFileProvider { // successfully logged in if response.hasPrefix("23") { - completionHandler(nil) + return } // needs password @@ -122,17 +125,20 @@ extension FTPFileProvider { completionHandler(error) } } + return } + + let spaceIndex = response.characters.index(of: " ") ?? response.startIndex + let code = Int(response.substring(to: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1 + let description = response.substring(from: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines) + let error = FileProviderFTPError(code: code, path: "", errorDescription: description) + completionHandler(error) + return } } if self.baseURL?.scheme == "ftps" { - self.execute(command: "AUTH TLS", on: task, completionHandler: { (response, error) in - if let error = error { - completionHandler(error) - return - } - + self.execute(command: "AUTH TLS", on: task, minLength: 0, completionHandler: { (response, error) in task.startSecureConnection() loginHandle() }) @@ -161,8 +167,12 @@ extension FTPFileProvider { } // not logged in else if response.hasPrefix("55") { - let error = NSError(domain: URLError.errorDomain, code: URLError.fileDoesNotExist.rawValue, userInfo: nil) + let spaceIndex = response.characters.index(of: " ") ?? response.startIndex + let code = Int(response.substring(to: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1 + let description = response.substring(from: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines) + let error = FileProviderFTPError(code: code, path: "", errorDescription: description) completionHandler(error) + return } } } @@ -214,8 +224,12 @@ extension FTPFileProvider { if response?.hasPrefix("35") ?? false { completionHandler(nil) } else { - let error = NSError(domain: URLError.errorDomain, code: URLError.resourceUnavailable.rawValue, userInfo: nil) + let spaceIndex = response?.characters.index(of: " ") ?? response?.startIndex + let code = Int((response?.substring(to: spaceIndex!).trimmingCharacters(in: .whitespacesAndNewlines))!) ?? -1 + let description = response?.substring(from: spaceIndex!).trimmingCharacters(in: .whitespacesAndNewlines) + let error = FileProviderFTPError(code: code, path: "", errorDescription: description) completionHandler(error) + return } } } @@ -288,12 +302,34 @@ extension FTPFileProvider { completionHandler([], error) return } + + guard let response = response else { + let badResponseError = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil) + completionHandler([], badResponseError) + return + } + + if response.hasPrefix("50") && useMLST { + self.ftpList(task, of: path, useMLST: false, completionHandler: completionHandler) + return + } + + if !response.hasPrefix("25") { + let spaceIndex = response.characters.index(of: " ") ?? response.startIndex + let code = Int(response.substring(to: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1 + let description = response.substring(from: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines) + let error = FileProviderFTPError(code: code, path: "", errorDescription: description) + + self.dispatch_queue.async { + completionHandler([], error) + } + return + } } } } func ftpRetrieve(_ task: FPSStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FPSStreamTask) -> Void)?, completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) { - // FIXME: retreiven't begain self.ftpPassive(task) { (host, port, error) in if let error = error { completionHandler(nil, error) @@ -307,7 +343,6 @@ extension FTPFileProvider { } // Send retreive command - // FIXME: use crlf instead of length self.execute(command: "REST \(position)\r\nRETR \(filePath)", on: task, minLength: 75, afterSend: { error in // starting passive task let timeout = self.session.configuration.timeoutIntervalForRequest @@ -358,6 +393,23 @@ extension FTPFileProvider { return } + guard let response = response else { + let badResponseError = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil) + completionHandler(nil, badResponseError) + return + } + + if !(response.hasPrefix("1") || !response.hasPrefix("2")) { + let spaceIndex = response.characters.index(of: " ") ?? response.startIndex + let code = Int(response.substring(to: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1 + let description = response.substring(from: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines) + let error = FileProviderFTPError(code: code, path: "", errorDescription: description) + + self.dispatch_queue.async { + completionHandler(nil, error) + } + return + } } } } @@ -377,7 +429,6 @@ extension FTPFileProvider { } // Send retreive command - // FIXME: use crlf instead of length self.execute(command: "STOR \(filePath)", on: task, minLength: 75, afterSend: { error in // starting passive task let timeout = self.session.configuration.timeoutIntervalForRequest @@ -437,6 +488,23 @@ extension FTPFileProvider { return } + guard let response = response else { + let badResponseError = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil) + completionHandler(badResponseError) + return + } + + if !(response.hasPrefix("1") || !response.hasPrefix("2")) { + let spaceIndex = response.characters.index(of: " ") ?? response.startIndex + let code = Int(response.substring(to: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1 + let description = response.substring(from: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines) + let error = FileProviderFTPError(code: code, path: "", errorDescription: description) + + self.dispatch_queue.async { + completionHandler(error) + } + return + } } } } @@ -590,3 +658,9 @@ extension FTPFileProvider { return file } } + +public struct FileProviderFTPError: Error { + public let code: Int + public let path: String + public let errorDescription: String? +}