From 5e2f911cda283282438b646e946c8315a6b8719b Mon Sep 17 00:00:00 2001 From: Amir Abbas Mousavian Date: Thu, 11 Aug 2016 01:06:53 +0430 Subject: [PATCH] Dropbox provider bug fixes --- FileProvider.podspec | 2 +- README.md | 10 ++-- Sources/DropboxFileProvider.swift | 31 ++++------- Sources/DropboxHelper.swift | 9 +-- Sources/FileProvider.swift | 6 +- Sources/SMBClient.swift | 88 ++++++++++++++++++++++++------ Sources/SMBTypes/SMB2Session.swift | 39 ++++++++----- Sources/SMBTypes/SMB2Tree.swift | 2 +- 8 files changed, 125 insertions(+), 62 deletions(-) diff --git a/FileProvider.podspec b/FileProvider.podspec index 2febaa3..0d2b302 100644 --- a/FileProvider.podspec +++ b/FileProvider.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "FileProvider" - s.version = "0.4.0" + s.version = "0.4.1" s.summary = "NSFileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS." # This description is used to generate tags and improve search results. diff --git a/README.md b/README.md index 8f13ef0..485d097 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,11 @@ Local and WebDAV providers are fully tested and can be used in production enviro ## Features - [x] **LocalFileProvider** a wrapper around `NSFileManager` with some additions like searching and reading a portion of file. -- [x] **WebDAVFileProvider** WebDAV protocol is usual file transmission system on Macs. -- [x] **DropboxFileProvider** *implemented but not tested* -- [ ] **SMBFileProvider** SMB/CIFS and SMB2/3 are file and printer sharing protocol which is originated from IBM & Microsoft and SMB2/3 is now replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!* -- [ ] **FTPFileProvider** -- [ ] **AmazonS3FileProvider** +- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission protocol standard, replaced FTP. +- [x] **DropboxFileProvider** A wrapper around Dropbox Web API. For now it has limitation in uploading files up to 150MB. +- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!*. SMB1/CIFS is depericated and very tricky to be implemented +- [ ] **FTPFileProvider** while depericated in 1990s, it's still in use on some Web hosts. +- [ ] **AmazonS3FileProvider** ## Requirements diff --git a/Sources/DropboxFileProvider.swift b/Sources/DropboxFileProvider.swift index 4bf28a4..6c680e8 100644 --- a/Sources/DropboxFileProvider.swift +++ b/Sources/DropboxFileProvider.swift @@ -35,11 +35,8 @@ public class DropboxFileProvider: NSObject, FileProviderBasic { return _session! } - public init? (baseURL: NSURL, credential: NSURLCredential?) { - if !["http", "https"].contains(baseURL.uw_scheme.lowercaseString) { - return nil - } - self.baseURL = baseURL + public init? (credential: NSURLCredential?) { + self.baseURL = nil dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT) //let url = baseURL.uw_absoluteString self.credential = credential @@ -69,7 +66,7 @@ public class DropboxFileProvider: NSObject, FileProviderBasic { self.delegateNotify(FileOperation.Create(path: path), error: error) } let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil + let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) : nil if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding), let json = self.jsonToDictionary(jsonStr), let file = self.mapToFileObject(json) { completionHandler(attributes: file, error: dbError) return @@ -159,7 +156,7 @@ extension DropboxFileProvider: FileProviderOperations { let task = session.dataTaskWithRequest(request) { (data, response, error) in if let response = response as? NSHTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "") : nil + let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "", errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) : nil defer { self.delegateNotify(operation, error: error ?? dbError) } @@ -184,7 +181,7 @@ extension DropboxFileProvider: FileProviderOperations { } public func copyPathToLocalFile(path: String, toLocalURL destURL: NSURL, completionHandler: SimpleCompletionHandler) { - let url = NSURL(string: "https://api.dropboxapi.com/2/files/download")! + let url = NSURL(string: "https://content.dropboxapi.com/2/files/download")! let request = NSMutableURLRequest(URL: url) request.HTTPMethod = "GET" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") @@ -194,7 +191,7 @@ extension DropboxFileProvider: FileProviderOperations { let task = session.downloadTaskWithRequest(request, completionHandler: { (cacheURL, response, error) in guard let cacheURL = cacheURL, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else { let code = FileProviderHTTPErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1) - let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil + let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: nil) : nil completionHandler?(error: dbError ?? error) return } @@ -216,7 +213,7 @@ extension DropboxFileProvider: FileProviderReadWrite { } public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) { - let url = NSURL(string: "https://api.dropboxapi.com/2/files/download")! + let url = NSURL(string: "https://content.dropboxapi.com/2/files/download")! let request = NSMutableURLRequest(URL: url) request.HTTPMethod = "GET" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") @@ -227,20 +224,14 @@ extension DropboxFileProvider: FileProviderReadWrite { } let requestDictionary = ["path": path] request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg") - let task = session.downloadTaskWithRequest(request, completionHandler: { (cacheURL, response, error) in - guard let cacheURL = cacheURL, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else { + let task = session.dataTaskWithRequest(request, completionHandler: { (datam, response, error) in + guard let data = datam, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else { let code = FileProviderHTTPErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1) - let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil + let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: datam ?? NSData(), encoding: NSUTF8StringEncoding)) : nil completionHandler(contents: nil, error: dbError ?? error) return } - let destURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).uw_URLByAppendingPathComponent(cacheURL.lastPathComponent ?? "tmpfile") - do { - try NSFileManager.defaultManager().moveItemAtURL(cacheURL, toURL: destURL) - completionHandler(contents: NSData(contentsOfURL: destURL), error: error) - } catch let e { - completionHandler(contents: nil, error: e) - } + completionHandler(contents: data, error: error) }) task.resume() } diff --git a/Sources/DropboxHelper.swift b/Sources/DropboxHelper.swift index 4f66de9..5e91c97 100644 --- a/Sources/DropboxHelper.swift +++ b/Sources/DropboxHelper.swift @@ -11,6 +11,7 @@ import Foundation public struct FileProviderDropboxError: ErrorType, CustomStringConvertible { public let code: FileProviderHTTPErrorCode public let path: String + public let errorDescription: String? public var description: String { return code.description @@ -40,7 +41,7 @@ internal extension DropboxFileProvider { } else { url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder")! requestDictionary["path"] = correctPath(path) - requestDictionary["recursive"] = NSNumber(bool: recursive) + requestDictionary["recursive"] = recursive } let request = NSMutableURLRequest(URL: url) request.HTTPMethod = "POST" @@ -50,7 +51,7 @@ internal extension DropboxFileProvider { let task = session.dataTaskWithRequest(request) { (data, response, error) in var responseError: FileProviderDropboxError? if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderDropboxError(code: rCode, path: path) + responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) } if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) { let json = self.jsonToDictionary(jsonStr) @@ -95,7 +96,7 @@ internal extension DropboxFileProvider { let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in var responseError: FileProviderDropboxError? if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderDropboxError(code: rCode, path: targetPath) + responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) } defer { self.delegateNotify(.Create(path: targetPath), error: responseError ?? error) @@ -135,7 +136,7 @@ internal extension DropboxFileProvider { let task = session.dataTaskWithRequest(request) { (data, response, error) in var responseError: FileProviderDropboxError? if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderDropboxError(code: rCode, path: startPath) + responseError = FileProviderDropboxError(code: rCode, path: startPath, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) } if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) { let json = self.jsonToDictionary(jsonStr) diff --git a/Sources/FileProvider.swift b/Sources/FileProvider.swift index 49f40fd..29d2000 100644 --- a/Sources/FileProvider.swift +++ b/Sources/FileProvider.swift @@ -177,7 +177,11 @@ extension FileProviderBasic { internal func correctPath(path: String?) -> String? { guard let path = path else { return nil } - return path.hasPrefix("/") ? path : "/" + path + var p = path.hasPrefix("/") ? path : "/" + path + if p.hasSuffix("/") { + p.removeAtIndex(p.endIndex.predecessor()) + } + return p } public func fileByUniqueName(filePath: String) -> String { diff --git a/Sources/SMBClient.swift b/Sources/SMBClient.swift index 7207130..465b43b 100644 --- a/Sources/SMBClient.swift +++ b/Sources/SMBClient.swift @@ -35,23 +35,79 @@ protocol SMBProtocolClientDelegate: class { func receivedSMB2Response(header: SMB2.Header, response: SMBResponse) } -class SMBProtocolClient: FPSStreamTask { +class SMB2ProtocolClient: FPSStreamTask { var currentMessageID: UInt64 = 0 + var sessionId: UInt64 = 0 weak var delegate: SMBProtocolClientDelegate? - func negotiateToSMB2() throws { - let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: 126, messageId: messageId(), treeId: 0, sessionId: 0) - currentMessageID += 1 - let negMessage = SMB2.NegotiateRequest(request: SMB2.NegotiateRequest.Header(capabilities: [])) - let data = createSMB2Message(smbHeader, message: negMessage) + func sendNegotiate(completionHandler: SimpleCompletionHandler) -> UInt64 { + let mId = messageId() + let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: 126, messageId: mId, treeId: 0, sessionId: 0) + let msg = SMB2.NegotiateRequest() + let data = createSMB2Message(smbHeader, message: msg) self.writeData(data, timeout: 0, completionHandler: { (e) in - return + completionHandler?(error: e) }) + return mId } - func sessionSetupForSMB2() -> SMB2.SessionSetupResponse? { - return nil + func sendSessionSetup(completionHandler: SimpleCompletionHandler) -> UInt64 { + let mId = messageId() + let smbHeader = SMB2.Header(command: .SESSION_SETUP, creditRequestResponse: sessionId > 0 ? 124 : 125, messageId: mId, treeId: 0, sessionId: sessionId) + let msg = SMB2.SessionSetupRequest(singing: []) + let data = createSMB2Message(smbHeader, message: msg) + self.writeData(data, timeout: 0, completionHandler: { (e) in + if self.sessionId == 0 { + self.readDataOfMinLength(64, maxLength: 65536, timeout: 30, completionHandler: { (data, eof, e2) in + // TODO: set session id + completionHandler?(error: e2 ?? e) + }) + } + }) + return mId + } + + func sendTreeConnect(completionHandler: SimpleCompletionHandler) -> UInt64 { + let req = self.currentRequest ?? self.originalRequest + guard let url = req?.URL, let host = url.host else { + return 0 + } + let mId = messageId() + let smbHeader = SMB2.Header(command: .TREE_CONNECT, creditRequestResponse: 123, messageId: mId, treeId: 0, sessionId: sessionId) + var share = "" + if let cmp = url.pathComponents where cmp.count > 0 { + share = cmp[0] + } + let tcHeader = SMB2.TreeConnectRequest.Header(flags: []) + let msg = SMB2.TreeConnectRequest(header: tcHeader, host: host, share: share) + let data = createSMB2Message(smbHeader, message: msg!) + self.writeData(data, timeout: 0, completionHandler: { (e) in + completionHandler?(error: e) + + }) + return mId + } + func sendTreeDisconnect(treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 { + let mId = messageId() + let smbHeader = SMB2.Header(command: .TREE_DISCONNECT, creditRequestResponse: 111, messageId: mId, treeId: treeId, sessionId: sessionId) + let msg = SMB2.TreeDisconnect() + let data = createSMB2Message(smbHeader, message: msg) + self.writeData(data, timeout: 0, completionHandler: { (e) in + completionHandler?(error: e) + }) + return mId + } + + func sendLogoff(treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 { + let mId = messageId() + let smbHeader = SMB2.Header(command: .LOGOFF, creditRequestResponse: 0, messageId: mId, treeId: 0, sessionId: sessionId) + let msg = SMB2.LogOff() + let data = createSMB2Message(smbHeader, message: msg) + self.writeData(data, timeout: 0, completionHandler: { (e) in + completionHandler?(error: e) + }) + return mId } func messageId() -> UInt64 { @@ -137,13 +193,13 @@ class SMBProtocolClient: FPSStreamTask { case .FLUSH: return (header, SMB2.FlushResponse(data: messageData)) case .READ: - return (header, nil) // FIXME: + return (header, SMB2.ReadRespone(data: messageData)) case .WRITE: - return (header, nil) // FIXME: + return (header, SMB2.WriteResponse(data: messageData)) case .LOCK: - return (header, nil) // FIXME: + return (header, SMB2.LockResponse(data: messageData)) case .IOCTL: - return (header, nil) + return (header, SMB2.IOCtlResponse(data: messageData)) case .CANCEL: return (header, nil) case .ECHO: @@ -151,11 +207,11 @@ class SMBProtocolClient: FPSStreamTask { case .QUERY_DIRECTORY: return (header, SMB2.QueryDirectoryResponse(data: messageData)) case .CHANGE_NOTIFY: - return (header, nil) // FIXME: + return (header, SMB2.ChangeNotifyResponse(data: messageData)) case .QUERY_INFO: - return (header, nil) // FIXME: + return (header, SMB2.QueryInfoResponse(data: messageData)) case .SET_INFO: - return (header, nil) // FIXME: + return (header, SMB2.SetInfoResponse(data: messageData)) case .OPLOCK_BREAK: return (header, nil) // FIXME: case .INVALID: diff --git a/Sources/SMBTypes/SMB2Session.swift b/Sources/SMBTypes/SMB2Session.swift index e2d908f..410452c 100644 --- a/Sources/SMBTypes/SMB2Session.swift +++ b/Sources/SMBTypes/SMB2Session.swift @@ -12,19 +12,25 @@ extension SMB2 { // MARK: SMB2 Negotiating struct NegotiateRequest: SMBRequest { - let request: NegotiateRequest.Header + let header: NegotiateRequest.Header let dialects: [UInt16] let contexts: [(type: NegotiateContextType, data: NSData)] - init(request: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = []) { - self.request = request + init(header: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = []) { + self.header = header + self.dialects = dialects + self.contexts = contexts + } + + init(dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = [],capabilities: GlobalCapabilities = [], clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) { + self.header = Header(capabilities: capabilities, clientStartTime: clientStartTime, guid: guid, signing: signing) self.dialects = dialects self.contexts = contexts } func data() -> NSData { - var request = self.request - request.dialectCount = UInt16(dialects.count) + var header = self.header + header.dialectCount = UInt16(dialects.count) let dialectData = NSMutableData() for dialect in dialects { var dialect = dialect @@ -32,8 +38,8 @@ extension SMB2 { } let pad = ((1024 - dialectData.length) % 8) dialectData.increaseLengthBy(pad) - request.contextOffset = UInt32(sizeof(request.dynamicType.self)) + UInt32(dialectData.length) - request.contextCount = UInt16(contexts.count) + header.contextOffset = UInt32(sizeof(header.dynamicType.self)) + UInt32(dialectData.length) + header.contextCount = UInt16(contexts.count) let contextData = NSMutableData() for context in contexts { @@ -43,7 +49,7 @@ extension SMB2 { contextData.increaseLengthBy(4) contextData.appendBytes(&dataLen, length: 2) } - let result = NSMutableData(data: encode(&request)) + let result = NSMutableData(data: encode(&header)) result.appendData(dialectData) result.appendData(contextData) return result @@ -52,7 +58,7 @@ extension SMB2 { struct Header { var size: UInt16 var dialectCount: UInt16 - let singing: NegotiateSinging + let signing: NegotiateSinging private let reserved: UInt16 let capabilities: GlobalCapabilities let guid: uuid_t @@ -64,13 +70,13 @@ extension SMB2 { return SMBTime(time: time) } - init(singing: NegotiateSinging = [.ENABLED], capabilities: GlobalCapabilities, guid: uuid_t? = nil, clientStartTime: SMBTime? = nil) { + init(capabilities: GlobalCapabilities, clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) { self.size = 36 self.dialectCount = 0 - self.singing = singing + self.signing = signing self.reserved = 0 self.capabilities = capabilities - self.guid = guid ?? (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + self.guid = guid ?? (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) if let clientStartTime = clientStartTime { let time = clientStartTime.time self.contextOffset = UInt32(time & 0xffffffff) @@ -179,6 +185,11 @@ extension SMB2 { self.buffer = buffer } + init(sessionId: UInt64 = 0, flags: SessionSetupRequest.Flags = [], singing: SessionSetupSinging = [.ENABLED], capabilities: GlobalCapabilities = [], securityData: NSData? = nil) { + self.header = Header(sessionId: sessionId, flags: flags, singing: singing, capabilities: capabilities) + self.buffer = securityData + } + func data() -> NSData { var header = self.header header.bufferOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(SessionSetupRequest.Header.self)) @@ -272,8 +283,8 @@ extension SMB2 { self.rawValue = rawValue } - static let ENABLED = NegotiateSinging(rawValue: 0x01) - static let REQUIRED = NegotiateSinging(rawValue: 0x02) + static let ENABLED = SessionSetupSinging(rawValue: 0x01) + static let REQUIRED = SessionSetupSinging(rawValue: 0x02) } // MARK: SMB2 Log off diff --git a/Sources/SMBTypes/SMB2Tree.swift b/Sources/SMBTypes/SMB2Tree.swift index 05b5fd6..c731a99 100644 --- a/Sources/SMBTypes/SMB2Tree.swift +++ b/Sources/SMBTypes/SMB2Tree.swift @@ -22,7 +22,7 @@ extension SMB2 { } init? (header: TreeConnectRequest.Header, host: String, share: String) { - guard !host.containsString("/") && !host.containsString("/") && !share.containsString("/") && !share.containsString("/") else { + guard !host.containsString("/") && !share.containsString("/") else { return nil } self.header = header