Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e2f911cda | |||
| f423834021 | |||
| 0772c87122 | |||
| 011c535760 | |||
| 468a2bc9e9 | |||
| 33be492499 | |||
| c4049b961f | |||
| 940c7c1028 | |||
| b4ace7e680 | |||
| eccbeb7174 | |||
| a077d000bc | |||
| 6a3ea633bf |
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FileProvider"
|
||||
s.version = "0.3.2"
|
||||
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.
|
||||
|
||||
@@ -7,6 +7,18 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
794C21FE1D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
|
||||
794C21FF1D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
|
||||
794C22001D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
|
||||
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
|
||||
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
|
||||
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
|
||||
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
|
||||
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
|
||||
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
|
||||
794C22121D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
|
||||
794C22131D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
|
||||
794C22141D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
|
||||
799396A71D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
|
||||
799396A81D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
|
||||
799396A91D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
|
||||
@@ -58,15 +70,16 @@
|
||||
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
|
||||
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
|
||||
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
|
||||
799396DD1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
|
||||
799396DE1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
|
||||
799396DF1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
|
||||
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropboxHelper.swift; sourceTree = "<group>"; };
|
||||
794C22091D5893F800EC49B8 /* SMB2Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Notification.swift; sourceTree = "<group>"; };
|
||||
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2QueryTypes.swift; sourceTree = "<group>"; };
|
||||
794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPSStreamTask.swift; sourceTree = "<group>"; };
|
||||
799396671D48B7F600086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
799396751D48B80D00086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
799396821D48B82700086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -91,7 +104,6 @@
|
||||
799396A21D48C02300086753 /* SMB2Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Tree.swift; sourceTree = "<group>"; };
|
||||
799396A31D48C02300086753 /* SMB2Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Types.swift; sourceTree = "<group>"; };
|
||||
799396A41D48C02300086753 /* SMBErrorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBErrorType.swift; sourceTree = "<group>"; };
|
||||
799396A51D48C02300086753 /* TCPSocketClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TCPSocketClient.swift; sourceTree = "<group>"; };
|
||||
799396A61D48C02300086753 /* WebDAVFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebDAVFileProvider.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -155,12 +167,13 @@
|
||||
799396991D48C02300086753 /* SMBTypes */,
|
||||
799396921D48C02300086753 /* AEXML.swift */,
|
||||
799396931D48C02300086753 /* DropboxFileProvider.swift */,
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
|
||||
799396941D48C02300086753 /* FileProvider.h */,
|
||||
799396951D48C02300086753 /* FileProvider.swift */,
|
||||
799396961D48C02300086753 /* LocalFileProvider.swift */,
|
||||
799396971D48C02300086753 /* SMBClient.swift */,
|
||||
799396981D48C02300086753 /* SMBFileProvider.swift */,
|
||||
799396A51D48C02300086753 /* TCPSocketClient.swift */,
|
||||
794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */,
|
||||
799396A61D48C02300086753 /* WebDAVFileProvider.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
@@ -170,16 +183,18 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7993969A1D48C02300086753 /* CIFSTypes.swift */,
|
||||
799396A41D48C02300086753 /* SMBErrorType.swift */,
|
||||
7993969B1D48C02300086753 /* SMB2DataTypes.swift */,
|
||||
7993969C1D48C02300086753 /* SMB2FileHandle.swift */,
|
||||
799396A31D48C02300086753 /* SMB2Types.swift */,
|
||||
799396A21D48C02300086753 /* SMB2Tree.swift */,
|
||||
799396A01D48C02300086753 /* SMB2Session.swift */,
|
||||
7993969D1D48C02300086753 /* SMB2FileOperation.swift */,
|
||||
7993969C1D48C02300086753 /* SMB2FileHandle.swift */,
|
||||
7993969E1D48C02300086753 /* SMB2IOCtl.swift */,
|
||||
7993969F1D48C02300086753 /* SMB2Query.swift */,
|
||||
799396A01D48C02300086753 /* SMB2Session.swift */,
|
||||
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */,
|
||||
794C22091D5893F800EC49B8 /* SMB2Notification.swift */,
|
||||
799396A11D48C02300086753 /* SMB2SetInfo.swift */,
|
||||
799396A21D48C02300086753 /* SMB2Tree.swift */,
|
||||
799396A31D48C02300086753 /* SMB2Types.swift */,
|
||||
799396A41D48C02300086753 /* SMBErrorType.swift */,
|
||||
);
|
||||
path = SMBTypes;
|
||||
sourceTree = "<group>";
|
||||
@@ -335,12 +350,14 @@
|
||||
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396DD1D48C02300086753 /* TCPSocketClient.swift in Sources */,
|
||||
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
794C21FE1D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
|
||||
799396BC1D48C02300086753 /* CIFSTypes.swift in Sources */,
|
||||
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
|
||||
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */,
|
||||
799396A71D48C02300086753 /* AEXML.swift in Sources */,
|
||||
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */,
|
||||
@@ -348,6 +365,7 @@
|
||||
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */,
|
||||
799396C21D48C02300086753 /* SMB2FileHandle.swift in Sources */,
|
||||
799396CB1D48C02300086753 /* SMB2Query.swift in Sources */,
|
||||
794C22121D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
|
||||
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
799396B01D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
799396B61D48C02300086753 /* SMBClient.swift in Sources */,
|
||||
@@ -361,12 +379,14 @@
|
||||
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396DE1D48C02300086753 /* TCPSocketClient.swift in Sources */,
|
||||
799396D81D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
794C21FF1D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
|
||||
799396BD1D48C02300086753 /* CIFSTypes.swift in Sources */,
|
||||
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
|
||||
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */,
|
||||
799396A81D48C02300086753 /* AEXML.swift in Sources */,
|
||||
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */,
|
||||
@@ -374,6 +394,7 @@
|
||||
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */,
|
||||
799396C31D48C02300086753 /* SMB2FileHandle.swift in Sources */,
|
||||
799396CC1D48C02300086753 /* SMB2Query.swift in Sources */,
|
||||
794C22131D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
|
||||
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
799396B11D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
799396B71D48C02300086753 /* SMBClient.swift in Sources */,
|
||||
@@ -387,12 +408,14 @@
|
||||
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396DF1D48C02300086753 /* TCPSocketClient.swift in Sources */,
|
||||
799396D91D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
794C22001D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
|
||||
799396BE1D48C02300086753 /* CIFSTypes.swift in Sources */,
|
||||
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
|
||||
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */,
|
||||
799396A91D48C02300086753 /* AEXML.swift in Sources */,
|
||||
799396D01D48C02300086753 /* SMB2Session.swift in Sources */,
|
||||
@@ -400,6 +423,7 @@
|
||||
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */,
|
||||
799396C41D48C02300086753 /* SMB2FileHandle.swift in Sources */,
|
||||
799396CD1D48C02300086753 /* SMB2Query.swift in Sources */,
|
||||
794C22141D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
|
||||
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
799396B21D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
799396B81D48C02300086753 /* SMBClient.swift in Sources */,
|
||||
@@ -778,6 +802,7 @@
|
||||
7993966E1D48B7F600086753 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
7993967A1D48B80D00086753 /* Build configuration list for PBXNativeTarget "FileProvider OSX" */ = {
|
||||
isa = XCConfigurationList;
|
||||
@@ -786,6 +811,7 @@
|
||||
7993967C1D48B80D00086753 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
799396871D48B82700086753 /* Build configuration list for PBXNativeTarget "FileProvider tvOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
@@ -794,6 +820,7 @@
|
||||
799396891D48B82700086753 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
|
||||
@@ -22,15 +22,15 @@ 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.
|
||||
- [ ] **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!*
|
||||
- [ ] **DropboxFileProvider** *partially implemented*
|
||||
- [ ] **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
|
||||
|
||||
- **Swift 2.2**
|
||||
- **Swift 2.2 or 2.3**
|
||||
- iOS 8.0 , OSX 10.10
|
||||
- XCode 7.3
|
||||
|
||||
@@ -81,7 +81,9 @@ You can't change the base url later. and all paths are related to this base url
|
||||
For remote file providers authentication may be necessary:
|
||||
|
||||
let credential = NSURLCredential(user: "user", password: "pass", persistence: NSURLCredentialPersistence.Permanent)
|
||||
let webdavProvider = WebDAVFileProvider(baseURL: "http://www.example.com/dav", credential: credential)
|
||||
let webdavProvider = WebDAVFileProvider(baseURL: NSURL(string: "http://www.example.com/dav")!, credential: credential)
|
||||
|
||||
* In case you want to connect non-secure servers for WebDAV (http) in iOS 9+ / macOS 10.11+ you should disable App Transport Security (ATS) according to [this guide.](https://gist.github.com/mlynch/284699d676fe9ed0abfa)
|
||||
|
||||
* For Dropbox, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token.
|
||||
|
||||
@@ -101,7 +103,7 @@ Your class should conforms `FileProviderDelegate` class:
|
||||
documentsProvider.delegate = self
|
||||
}
|
||||
|
||||
func fileproviderSucceed(fileProvider: FileProvider, operation: FileOperation) {
|
||||
func fileproviderSucceed(fileProvider: FileProviderOperations, operation: FileOperation) {
|
||||
switch operation {
|
||||
case .Copy(source: let source, destination: let dest):
|
||||
NSLog("\(source) copied to \(dest).")
|
||||
@@ -112,7 +114,7 @@ Your class should conforms `FileProviderDelegate` class:
|
||||
}
|
||||
}
|
||||
|
||||
func fileproviderFailed(fileProvider: FileProvider, operation: FileOperation) {
|
||||
func fileproviderFailed(fileProvider: FileProviderOperations, operation: FileOperation) {
|
||||
switch operation {
|
||||
case .Copy(source: let source, destination: let dest):
|
||||
NSLog("copy of \(source) failed.")
|
||||
@@ -123,7 +125,7 @@ Your class should conforms `FileProviderDelegate` class:
|
||||
}
|
||||
}
|
||||
|
||||
func fileproviderProgress(fileProvider: FileProvider, operation: FileOperation, progress: Float) {
|
||||
func fileproviderProgress(fileProvider: FileProviderOperations, operation: FileOperation, progress: Float) {
|
||||
switch operation {
|
||||
case .Copy(source: let source, destination: let dest):
|
||||
NSLog("Copy\(source) to \(dest): \(progress * 100) completed.")
|
||||
@@ -153,26 +155,36 @@ There is a `FileObject` class which holds file attributes like size and creation
|
||||
For a single file:
|
||||
|
||||
documentsProvider.attributesOfItemAtPath(path: "/file.txt", completionHandler: {
|
||||
(attributes: LocalFileObject?, error: ErrorType?) -> Void} in
|
||||
(attributes: LocalFileObject?, error: ErrorType?) -> Void in
|
||||
if let attributes = attributes {
|
||||
print("File Size: \(attributes.size)")
|
||||
print("Creation Date: \(attributes.createdDate)")
|
||||
print("Modification Date: \(modifiedDate)")
|
||||
print("Is Read Only: \(isReadOnly)")
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
To get list of files in a directory:
|
||||
|
||||
documentsProvider.contentsOfDirectoryAtPath(path: "/", completionHandler: {
|
||||
(contents: [LocalFileObject], error: ErrorType?) -> Void} in
|
||||
(contents: [LocalFileObject], error: ErrorType?) -> Void in
|
||||
for file in contents {
|
||||
print("Name: \(attributes.name)")
|
||||
print("Size: \(attributes.size)")
|
||||
print("Creation Date: \(attributes.createdDate)")
|
||||
print("Modification Date: \(modifiedDate)")
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
To get size of strage and used/free space:
|
||||
|
||||
func storageProperties(completionHandler: {(total: Int64, used: Int64) -> Void in
|
||||
print("Total Storage Space: \(total)")
|
||||
print("Used Space: \(used)")
|
||||
print("Free Space: \(total - frees)")
|
||||
})
|
||||
|
||||
* if this function is unavailable on provider or an error has been occurred, total space will be reported "-1" and used space "0"
|
||||
|
||||
### Change current directory
|
||||
|
||||
@@ -211,7 +223,7 @@ Move file old.txt to new.txt in current path:
|
||||
|
||||
### Retrieve Content of File
|
||||
|
||||
THere is two method for this purpose, one of them loads entire file into NSData and another can load a portion of file.
|
||||
There is two method for this purpose, one of them loads entire file into NSData and another can load a portion of file.
|
||||
|
||||
documentsProvider.contentsAtPath(path: "old.txt", completionHandler: {
|
||||
(contents: NSData?, error: ErrorType?) -> Void
|
||||
@@ -252,6 +264,13 @@ You can monitor updates in some file system (Local and SMB2), there is three met
|
||||
|
||||
We would love for you to contribute to **FileProvider**, check the `LICENSE` file for more info.
|
||||
|
||||
## Projects in use
|
||||
|
||||
* [EDM - Browse and Receive Files](https://itunes.apple.com/us/app/edm-browse-and-receive-files/id948397575?ls=1&mt=8)
|
||||
* [File Manager - PDF Reader & Music Player](https://itunes.apple.com/us/app/file-manager-pdf-reader-music/id1017809685?ls=1&mt=8)
|
||||
|
||||
If you used this library in your project, you can open an issue to inform us.
|
||||
|
||||
## Meta
|
||||
|
||||
Amir-Abbas Mousavian – [@amosavian](https://twitter.com/amosavian)
|
||||
@@ -260,7 +279,7 @@ Distributed under the MIT license. See `LICENSE` for more information.
|
||||
|
||||
[https://github.com/yourname/github-link](https://github.com/dbader/)
|
||||
|
||||
[swift-image]:https://img.shields.io/badge/swift-2.2-green.svg
|
||||
[swift-image]:https://img.shields.io/badge/swift-2.2%2C%202.3-green.svg
|
||||
[swift-url]: https://swift.org/
|
||||
[license-image]: https://img.shields.io/badge/License-MIT-blue.svg
|
||||
[license-url]: LICENSE
|
||||
|
||||
+130
-137
@@ -7,46 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum FileProviderDropboxErrorCode: Int {
|
||||
case BadInputParameter = 400
|
||||
case ExpiredToken = 401
|
||||
case Forbidden = 403
|
||||
case Endpoint = 409
|
||||
case TooManyRequests = 429
|
||||
case InternalServer = 500
|
||||
case BadGateway = 502
|
||||
}
|
||||
|
||||
public struct FileProviderDropboxError: ErrorType, CustomStringConvertible {
|
||||
public let code: FileProviderDropboxErrorCode
|
||||
public let path: String
|
||||
|
||||
public var description: String {
|
||||
switch code {
|
||||
case .BadInputParameter: return "Bad input parameter."
|
||||
case .ExpiredToken: return "Bad or expired token. To fix this, you should re-authenticate the user."
|
||||
case .Forbidden: return "Forbidden."
|
||||
case .Endpoint: return "Endpoint-specific error."
|
||||
case .TooManyRequests: return "Your app is making too many requests"
|
||||
case .InternalServer: return "An error occurred on the Dropbox servers."
|
||||
case .BadGateway: return "An error occurred on the Dropbox servers."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class DropboxFileObject: FileObject {
|
||||
public let serverTime: NSDate?
|
||||
public let id: String?
|
||||
public let rev: String?
|
||||
|
||||
public init(absoluteURL: NSURL, name: String, path: String, size: Int64, serverTime: NSDate?, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, id: String?, rev: String?) {
|
||||
self.serverTime = serverTime
|
||||
self.id = id
|
||||
self.rev = rev
|
||||
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
}
|
||||
}
|
||||
import CoreGraphics
|
||||
|
||||
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
|
||||
// in case of using this class with unencrypted HTTP connection.
|
||||
@@ -65,7 +26,7 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
public let credential: NSURLCredential?
|
||||
|
||||
private var _session: NSURLSession?
|
||||
private var session: NSURLSession {
|
||||
internal var session: NSURLSession {
|
||||
if _session == nil {
|
||||
let queue = NSOperationQueue()
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
@@ -74,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
|
||||
@@ -89,11 +47,13 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
}
|
||||
|
||||
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void)) {
|
||||
NotImplemented()
|
||||
list(path) { (contents, cursor, error) in
|
||||
completionHandler(contents: contents, error: error)
|
||||
}
|
||||
}
|
||||
|
||||
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
|
||||
let url = NSURL(string: "https://api.dropboxapi.com/2/files/list_revisions")!
|
||||
let url = NSURL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
@@ -105,14 +65,11 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
defer {
|
||||
self.delegateNotify(FileOperation.Create(path: path), error: error)
|
||||
}
|
||||
let code = FileProviderDropboxErrorCode(rawValue: response.statusCode)
|
||||
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil
|
||||
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
|
||||
let json = self.jsonToDictionary(jsonStr)
|
||||
if (json?["is_deleted"] as? NSNumber)?.boolValue ?? false, let entries = json?["entries"] as? [AnyObject] where entries.count > 0 , let entry = entries[0] as? [String: AnyObject], let file = self.mapToFileObject(entry) {
|
||||
completionHandler(attributes: file, error: dbError)
|
||||
return
|
||||
}
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
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
|
||||
}
|
||||
completionHandler(attributes: nil, error: dbError)
|
||||
return
|
||||
@@ -122,6 +79,23 @@ public class DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
|
||||
let url = NSURL(string: "https://api.dropboxapi.com/2/users/get_space_usage")!
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding), let json = self.jsonToDictionary(jsonStr) {
|
||||
let totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.longLongValue ?? -1
|
||||
let usedSize = (json["used"] as? NSNumber)?.longLongValue ?? 0
|
||||
completionHandler(total: totalSize, used: usedSize)
|
||||
return
|
||||
}
|
||||
completionHandler(total: -1, used: 0)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public weak var fileOperationDelegate: FileOperationDelegate?
|
||||
}
|
||||
|
||||
@@ -132,7 +106,7 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
public func createFile(fileAttribs: FileObject, atPath path: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
|
||||
NotImplemented()
|
||||
self.writeContentsAtPath(path, contents: data ?? NSData(), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
@@ -181,8 +155,8 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
if let response = response as? NSHTTPURLResponse {
|
||||
let code = FileProviderDropboxErrorCode(rawValue: response.statusCode)
|
||||
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "") : nil
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
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)
|
||||
}
|
||||
@@ -198,32 +172,37 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
NotImplemented()
|
||||
let request = NSMutableURLRequest(URL: absoluteURL(toPath))
|
||||
request.HTTPMethod = "PUT"
|
||||
let task = session.uploadTaskWithRequest(request, fromFile: localFile) { (data, response, error) in
|
||||
guard let data = NSData(contentsOfURL: localFile) else {
|
||||
let error = throwError(localFile.uw_absoluteString, code: NSURLError.FileDoesNotExist)
|
||||
completionHandler?(error: error)
|
||||
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: error)
|
||||
return
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": localFile.uw_absoluteString, "dest": toPath])
|
||||
task.resume()
|
||||
upload_simple(toPath, data: data, overwrite: true, operation: .Copy(source: localFile.absoluteString, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
|
||||
NotImplemented()
|
||||
let request = NSMutableURLRequest(URL: absoluteURL(path))
|
||||
let task = session.downloadTaskWithRequest(request) { (sourceFileURL, response, error) in
|
||||
if let sourceFileURL = sourceFileURL {
|
||||
do {
|
||||
try NSFileManager.defaultManager().copyItemAtURL(sourceFileURL, toURL: toLocalURL)
|
||||
} catch let e {
|
||||
completionHandler?(error: e)
|
||||
return
|
||||
}
|
||||
public func copyPathToLocalFile(path: String, toLocalURL destURL: NSURL, completionHandler: SimpleCompletionHandler) {
|
||||
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")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
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 code = FileProviderHTTPErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1)
|
||||
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: nil) : nil
|
||||
completionHandler?(error: dbError ?? error)
|
||||
return
|
||||
}
|
||||
completionHandler?(error: error)
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.uw_absoluteString])
|
||||
do {
|
||||
try NSFileManager.defaultManager().moveItemAtURL(cacheURL, toURL: destURL)
|
||||
completionHandler?(error: nil)
|
||||
} catch let e {
|
||||
completionHandler?(error: e)
|
||||
}
|
||||
})
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": destURL.uw_absoluteString])
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
@@ -234,12 +213,10 @@ 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")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
if length > 0 {
|
||||
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
|
||||
} else if offset > 0 && length < 0 {
|
||||
@@ -247,51 +224,35 @@ 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 code = FileProviderDropboxErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1)
|
||||
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil
|
||||
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, 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()
|
||||
}
|
||||
|
||||
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
NotImplemented()
|
||||
let url = atomically ? absoluteURL(path).uw_URLByAppendingPathExtension("tmp") : absoluteURL(path)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "PUT"
|
||||
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
|
||||
defer {
|
||||
self.delegateNotify(.Modify(path: path), error: error)
|
||||
}
|
||||
if atomically {
|
||||
self.moveItemAtPath((path as NSString).stringByAppendingPathExtension("tmp")!, toPath: path, completionHandler: completionHandler)
|
||||
}
|
||||
if let error = error {
|
||||
// If there is no error, completionHandler has been executed by move command
|
||||
completionHandler?(error: error)
|
||||
}
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Modify", "source": path])
|
||||
task.resume()
|
||||
// FIXME: remove 150MB restriction
|
||||
upload_simple(path, data: data, overwrite: true, operation: .Modify(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
|
||||
NotImplemented()
|
||||
var foundFiles = [DropboxFileObject]()
|
||||
search(path, query: query, foundItem: { (file) in
|
||||
foundFiles.append(file)
|
||||
foundItemHandler?(file)
|
||||
}, completionHandler: { (error) in
|
||||
completionHandler(files: foundFiles, error: error)
|
||||
})
|
||||
}
|
||||
|
||||
private func registerNotifcation(path: String, eventHandler: (() -> Void)) {
|
||||
/* There is two ways to monitor folders chaging in Dropbox. Either using webooks
|
||||
/* There is two ways to monitor folders changing in Dropbox. Either using webooks
|
||||
* which means you have to implement a server to translate it to push notifications
|
||||
* or using apiv2 list_folder/longpoll method. The second one is implemeted here.
|
||||
* Tough webhooks are much more efficient, longpoll is much simpler to implement!
|
||||
@@ -302,39 +263,71 @@ extension DropboxFileProvider: FileProviderReadWrite {
|
||||
private func unregisterNotifcation(path: String) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
// TODO: Implement /copy_reference, /get_temporary_link, /save_url, /get_account & /get_current_account
|
||||
}
|
||||
|
||||
internal extension DropboxFileProvider {
|
||||
private func mapToFileObject(jsonStr: String) -> DropboxFileObject? {
|
||||
guard let json = self.jsonToDictionary(jsonStr) else { return nil }
|
||||
return self.mapToFileObject(json)
|
||||
extension DropboxFileProvider: ExtendedFileProvider {
|
||||
public func thumbnailOfFileSupported(path: String) -> Bool {
|
||||
switch (path as NSString).pathExtension.lowercaseString {
|
||||
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
|
||||
return true
|
||||
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
|
||||
return true
|
||||
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
|
||||
return true
|
||||
case "rtf":
|
||||
return true*/
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func mapToFileObject(json: [String: AnyObject]) -> DropboxFileObject? {
|
||||
guard let name = json["name"] as? String else { return nil }
|
||||
guard let path = json["path_display"] as? String else { return nil }
|
||||
let href = NSURL(string: path)!
|
||||
let size = (json["size"] as? NSNumber)?.longLongValue ?? -1
|
||||
let serverTime = resolveDate(json["server_modified"] as? String ?? "")
|
||||
let modifiedDate = resolveDate(json["client_modified"] as? String ?? "")
|
||||
let isDirectory = (json[".tag"] as? String) == "folder"
|
||||
let isReadonly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
|
||||
let id = json["id"] as? String
|
||||
let rev = json["id"] as? String
|
||||
return DropboxFileObject(absoluteURL: href, name: name, path: path, size: size, serverTime: serverTime, createdDate: nil, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isHidden: false, isReadOnly: isReadonly, id: id, rev: rev)
|
||||
public func propertiesOfFileSupported(path: String) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
private func delegateNotify(operation: FileOperation, error: ErrorType?) {
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
public func thumbnailOfFileAtPath(path: String, dimension: CGSize, completionHandler: ((image: ImageClass?, error: ErrorType?) -> Void)) {
|
||||
let url: NSURL
|
||||
switch (path as NSString).pathExtension.lowercaseString {
|
||||
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
|
||||
url = NSURL(string: "https://content.dropboxapi.com/2/files/get_thumbnail")!
|
||||
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
|
||||
fallthrough
|
||||
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
|
||||
fallthrough
|
||||
case "rtf":
|
||||
url = NSURL(string: "https://content.dropboxapi.com/2/files/get_preview")!*/
|
||||
default:
|
||||
return
|
||||
}
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
var requestDictionary = ["path": path]
|
||||
requestDictionary["format"] = "jpeg"
|
||||
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))"
|
||||
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
self.session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
var image: ImageClass? = nil
|
||||
if let r = response as? NSHTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = self.jsonToDictionary(result) {
|
||||
if jsonResult["error"] != nil {
|
||||
completionHandler(image: nil, error: self.throwError(path, code: NSURLError.CannotDecodeRawData))
|
||||
}
|
||||
}
|
||||
})
|
||||
if let data = data {
|
||||
image = ImageClass(data: data)
|
||||
}
|
||||
completionHandler(image: image, error: error)
|
||||
}
|
||||
}
|
||||
|
||||
public func propertiesOfFileAtPath(path: String, completionHandler: ((propertiesDictionary: [String : AnyObject], keys: [String], error: ErrorType?) -> Void)) {
|
||||
NotImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProvider {}
|
||||
|
||||
// MARK: URLSession delegate
|
||||
extension DropboxFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
|
||||
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// DropboxHelper.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian on 5/18/95.
|
||||
//
|
||||
//
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public final class DropboxFileObject: FileObject {
|
||||
public let serverTime: NSDate?
|
||||
public let id: String?
|
||||
public let rev: String?
|
||||
|
||||
public init(name: String, path: String, size: Int64 = -1, serverTime: NSDate? = nil, modifiedDate: NSDate? = nil, fileType: FileType = .Regular, isHidden: Bool = false, isReadOnly: Bool = false, id: String? = nil, rev: String? = nil) {
|
||||
self.serverTime = serverTime
|
||||
self.id = id
|
||||
self.rev = rev
|
||||
super.init(absoluteURL: NSURL(string: path), name: name, path: path, size: size, createdDate: nil, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
}
|
||||
}
|
||||
|
||||
internal extension DropboxFileProvider {
|
||||
func list(path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: ((contents: [FileObject], cursor: String?, error: ErrorType?) -> Void)) {
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: NSURL
|
||||
if let cursor = cursor {
|
||||
url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
|
||||
requestDictionary["cursor"] = cursor
|
||||
} else {
|
||||
url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder")!
|
||||
requestDictionary["path"] = correctPath(path)
|
||||
requestDictionary["recursive"] = recursive
|
||||
}
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
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, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
|
||||
let json = self.jsonToDictionary(jsonStr)
|
||||
if let entries = json?["entries"] as? [AnyObject] where entries.count > 0 {
|
||||
var files = prevContents
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
|
||||
files.append(file)
|
||||
}
|
||||
}
|
||||
let ncursor = json?["cursor"] as? String
|
||||
let hasmore = (json?["has_more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore {
|
||||
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(contents: files, cursor: ncursor, error: responseError ?? error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(contents: [], cursor: nil, error: responseError ?? error)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func upload_simple(targetPath: String, data: NSData, modifiedDate: NSDate = NSDate(), overwrite: Bool, operation: FileOperation, completionHandler: SimpleCompletionHandler) {
|
||||
assert(data.length < 150*1024*1024, "Maximum size of allowed size to upload is 150MB")
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: NSURL
|
||||
url = NSURL(string: "https://content.dropboxapi.com/2/files/upload")!
|
||||
requestDictionary["path"] = correctPath(targetPath)
|
||||
requestDictionary["mode"] = overwrite ? "overwrite" : "add"
|
||||
let dateFormatter = NSDateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
|
||||
requestDictionary["client_modified"] = dateFormatter.stringFromDate(modifiedDate)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
request.HTTPBody = data
|
||||
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, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
|
||||
}
|
||||
defer {
|
||||
self.delegateNotify(.Create(path: targetPath), error: responseError ?? error)
|
||||
}
|
||||
completionHandler?(error: responseError ?? error)
|
||||
}
|
||||
var dic: [String: AnyObject] = ["type": operation.description]
|
||||
switch operation {
|
||||
case .Create(path: let s):
|
||||
dic["source"] = s
|
||||
case .Copy(source: let s, destination: let d):
|
||||
dic["source"] = s
|
||||
dic["dest"] = d
|
||||
case .Modify(path: let s):
|
||||
dic["source"] = s
|
||||
case .Move(source: let s, destination: let d):
|
||||
dic["source"] = s
|
||||
dic["dest"] = d
|
||||
default:
|
||||
break
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(dic)
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func search(startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:((file: DropboxFileObject) -> Void), completionHandler: ((error: ErrorType?) -> Void)) {
|
||||
let url = NSURL(string: "https://api.dropboxapi.com/2/files/search")!
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
var requestDictionary: [String: AnyObject] = ["path": startPath]
|
||||
requestDictionary["query"] = query
|
||||
requestDictionary["start"] = start
|
||||
requestDictionary["max_results"] = maxResultPerPage
|
||||
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
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, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
|
||||
let json = self.jsonToDictionary(jsonStr)
|
||||
if let entries = json?["matches"] as? [AnyObject] where entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
|
||||
foundItem(file: file)
|
||||
}
|
||||
}
|
||||
let rstart = json?["start"] as? Int
|
||||
let hasmore = (json?["more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore, let rstart = rstart {
|
||||
self.search(startPath, query: query, start: rstart + entries.count, maxResultPerPage: maxResultPerPage, foundItem: foundItem, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(error: responseError ?? error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(error: responseError ?? error)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
internal extension DropboxFileProvider {
|
||||
func mapToFileObject(jsonStr: String) -> DropboxFileObject? {
|
||||
guard let json = self.jsonToDictionary(jsonStr) else { return nil }
|
||||
return self.mapToFileObject(json)
|
||||
}
|
||||
|
||||
func mapToFileObject(json: [String: AnyObject]) -> DropboxFileObject? {
|
||||
guard let name = json["name"] as? String else { return nil }
|
||||
guard let path = json["path_display"] as? String else { return nil }
|
||||
let size = (json["size"] as? NSNumber)?.longLongValue ?? -1
|
||||
let serverTime = resolveDate(json["server_modified"] as? String ?? "")
|
||||
let modifiedDate = resolveDate(json["client_modified"] as? String ?? "")
|
||||
let isDirectory = (json[".tag"] as? String) == "folder"
|
||||
let isReadonly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
|
||||
let id = json["id"] as? String
|
||||
let rev = json["id"] as? String
|
||||
return DropboxFileObject(name: name, path: path, size: size, serverTime: serverTime, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isReadOnly: isReadonly, id: id, rev: rev)
|
||||
}
|
||||
|
||||
func delegateNotify(operation: FileOperation, error: ErrorType?) {
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
//
|
||||
// FPSStreamTask.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
private var lasttaskIdAssociated = 1_000_000_000
|
||||
|
||||
|
||||
/// This class is a replica of NSURLSessionStreamTask with same api for iOS 7/8
|
||||
/// while it will fallback to NSURLSessionStreamTask in iOS 9.
|
||||
@objc
|
||||
public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
|
||||
private var inputStream: NSInputStream?
|
||||
private var outputStream: NSOutputStream?
|
||||
|
||||
private var dispatch_queue: dispatch_queue_t!
|
||||
internal var _underlyingSession: NSURLSession
|
||||
private var streamDelegate: FPSStreamDelegate? {
|
||||
return (_underlyingSession.delegate as? FPSStreamDelegate)
|
||||
}
|
||||
private var _taskIdentifier: Int
|
||||
|
||||
@available(iOS 9.0, OSX 10.11, *)
|
||||
static var streamTasks = [Int: NSURLSessionStreamTask]()
|
||||
|
||||
@available(iOS 9.0, OSX 10.11, *)
|
||||
internal var _underlyingTask: NSURLSessionStreamTask? {
|
||||
return FPSStreamTask.streamTasks[_taskIdentifier]
|
||||
}
|
||||
|
||||
public override var taskIdentifier: Int {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.taskIdentifier
|
||||
} else {
|
||||
return _taskIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
private var _state: NSURLSessionTaskState = .Suspended
|
||||
override public var state: NSURLSessionTaskState {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.state
|
||||
} else {
|
||||
return _state
|
||||
}
|
||||
}
|
||||
|
||||
override public var originalRequest: NSURLRequest? {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.originalRequest
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
override public var currentRequest: NSURLRequest? {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.currentRequest
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var _countOfBytesSent: Int64 = 0
|
||||
private var _countOfBytesRecieved: Int64 = 0
|
||||
|
||||
override public var countOfBytesSent: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.countOfBytesSent
|
||||
} else {
|
||||
return _countOfBytesSent
|
||||
}
|
||||
}
|
||||
|
||||
override public var countOfBytesReceived: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.countOfBytesReceived
|
||||
} else {
|
||||
return _countOfBytesRecieved
|
||||
}
|
||||
}
|
||||
|
||||
override public var countOfBytesExpectedToSend: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.countOfBytesExpectedToSend
|
||||
} else {
|
||||
return Int64(dataToBeSent.length)
|
||||
}
|
||||
}
|
||||
|
||||
override public var countOfBytesExpectedToReceive: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.countOfBytesExpectedToReceive
|
||||
} else {
|
||||
return Int64(dataReceived.length)
|
||||
}
|
||||
}
|
||||
|
||||
override public init() {
|
||||
fatalError("Use NSURLSession.fpstreamTask() method")
|
||||
}
|
||||
|
||||
var host: (hostname: String, port: Int)?
|
||||
var service: NSNetService?
|
||||
|
||||
internal init(session: NSURLSession, host: String, port: Int) {
|
||||
self._underlyingSession = session
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
let task = session.streamTaskWithHostName(host, port: port)
|
||||
self._taskIdentifier = task.taskIdentifier
|
||||
FPSStreamTask.streamTasks[_taskIdentifier] = task
|
||||
} else {
|
||||
lasttaskIdAssociated += 1
|
||||
self._taskIdentifier = lasttaskIdAssociated
|
||||
self.host = (host, port)
|
||||
self.dispatch_queue = dispatch_queue_create("FSPStreamTask", DISPATCH_QUEUE_CONCURRENT)
|
||||
}
|
||||
}
|
||||
|
||||
internal init(session: NSURLSession, netService: NSNetService) {
|
||||
self._underlyingSession = session
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
let task = session.streamTaskWithNetService(netService)
|
||||
self._taskIdentifier = task.taskIdentifier
|
||||
FPSStreamTask.streamTasks[_taskIdentifier] = task
|
||||
} else {
|
||||
lasttaskIdAssociated += 1
|
||||
self._taskIdentifier = lasttaskIdAssociated
|
||||
self.service = netService
|
||||
self.dispatch_queue = dispatch_queue_create("FSPStreamTask", DISPATCH_QUEUE_CONCURRENT)
|
||||
}
|
||||
}
|
||||
|
||||
override public func cancel() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.cancel()
|
||||
} else {
|
||||
self._state = .Canceling
|
||||
inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
|
||||
outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
|
||||
|
||||
self.inputStream?.close()
|
||||
self.outputStream?.close()
|
||||
|
||||
self.inputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
self.outputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
|
||||
self.inputStream?.delegate = nil
|
||||
self.outputStream?.delegate = nil
|
||||
|
||||
self.inputStream = nil
|
||||
self.outputStream = nil
|
||||
|
||||
self._state = .Completed
|
||||
self._countOfBytesSent = 0
|
||||
self._countOfBytesRecieved = 0
|
||||
}
|
||||
}
|
||||
|
||||
var _error: NSError? = nil
|
||||
override public var error: NSError? {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.error
|
||||
} else {
|
||||
return _error
|
||||
}
|
||||
}
|
||||
|
||||
override public func suspend() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.suspend()
|
||||
} else {
|
||||
inputStream?.close()
|
||||
outputStream?.close()
|
||||
streamDelegate?.URLSession?(_underlyingSession, readClosedForStreamTask: self)
|
||||
streamDelegate?.URLSession?(_underlyingSession, writeClosedForStreamTask: self)
|
||||
self._state = .Suspended
|
||||
}
|
||||
}
|
||||
|
||||
override public func resume() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.resume()
|
||||
} else {
|
||||
var readStream : Unmanaged<CFReadStream>?
|
||||
var writeStream : Unmanaged<CFWriteStream>?
|
||||
|
||||
if inputStream == nil || outputStream == nil {
|
||||
if let host = host {
|
||||
let hostRef: CFString = NSString(string: host.hostname)
|
||||
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, hostRef, UInt32(host.port), &readStream, &writeStream)
|
||||
} else if let service = service {
|
||||
let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain, service.type, service.name, Int32(service.port))
|
||||
CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream)
|
||||
}
|
||||
|
||||
inputStream = readStream?.takeRetainedValue()
|
||||
outputStream = writeStream?.takeRetainedValue()
|
||||
guard let inputStream = inputStream, outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
streamDelegate?.URLSession?(self._underlyingSession, streamTask: self, didBecomeInputStream: inputStream, outputStream: outputStream)
|
||||
}
|
||||
|
||||
guard let inputStream = inputStream, outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
|
||||
inputStream.delegate = self
|
||||
outputStream.delegate = self
|
||||
|
||||
dispatch_sync(dispatch_queue, {
|
||||
inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
})
|
||||
|
||||
inputStream.open()
|
||||
outputStream.open()
|
||||
|
||||
_state = .Running
|
||||
}
|
||||
}
|
||||
|
||||
private let dataToBeSent: NSMutableData = NSMutableData()
|
||||
private let dataReceived: NSMutableData = NSMutableData()
|
||||
|
||||
/* Read minBytes, or at most maxBytes bytes and invoke the completion
|
||||
* handler on the sessions delegate queue with the data or an error.
|
||||
* If an error occurs, any outstanding reads will also fail, and new
|
||||
* read requests will error out immediately.
|
||||
*/
|
||||
public func readDataOfMinLength(minBytes: Int, maxLength maxBytes: Int, timeout: NSTimeInterval, completionHandler: (NSData?, Bool, NSError?) -> Void) {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.readDataOfMinLength(minBytes, maxLength: maxBytes, timeout: timeout, completionHandler: completionHandler)
|
||||
} else {
|
||||
guard let inputStream = inputStream else {
|
||||
return
|
||||
}
|
||||
var timedOut: Bool = false
|
||||
dispatch_async(dispatch_queue) {
|
||||
if timeout > 0 {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * 1_000_000_000)), self.dispatch_queue, {
|
||||
timedOut = true
|
||||
completionHandler(nil, inputStream.streamStatus == .AtEnd, inputStream.streamError)
|
||||
})
|
||||
}
|
||||
while (self.dataReceived.length == 0 || self.dataReceived.length < minBytes) && !timedOut {
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
let dR = NSMutableData()
|
||||
if self.dataReceived.length > maxBytes {
|
||||
let range = NSRange(location: 0, length: maxBytes - 1)
|
||||
dR.appendData(self.dataReceived.subdataWithRange(range))
|
||||
self.dataReceived.replaceBytesInRange(range, withBytes: nil, length: 0)
|
||||
} else {
|
||||
dR.appendData(self.dataReceived)
|
||||
self.dataReceived.length = 0
|
||||
}
|
||||
completionHandler(dR, inputStream.streamStatus == .AtEnd, inputStream.streamError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the data completely to the underlying socket. If all the
|
||||
* bytes have not been written by the timeout, a timeout error will
|
||||
* occur. Note that invocation of the completion handler does not
|
||||
* guarantee that the remote side has received all the bytes, only
|
||||
* that they have been written to the kernel. */
|
||||
public func writeData(data: NSData, timeout: NSTimeInterval, completionHandler: (NSError?) -> Void) {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.writeData(data, timeout: timeout, completionHandler: completionHandler)
|
||||
} else {
|
||||
guard let outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
var timedOut: Bool = false
|
||||
dispatch_async(dispatch_queue) {
|
||||
if timeout > 0 {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * 1_000_000_000)), self.dispatch_queue, {
|
||||
timedOut = true
|
||||
completionHandler(self._error)
|
||||
})
|
||||
}
|
||||
|
||||
self.dataToBeSent.appendData(data)
|
||||
while !outputStream.hasSpaceAvailable && !timedOut {
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
if self.dataToBeSent.length > 0 {
|
||||
let bytesWritten = outputStream.write(UnsafePointer(self.dataToBeSent.bytes), maxLength: self.dataToBeSent.length) ?? -1
|
||||
if bytesWritten > 0 {
|
||||
let range = NSRange(location: 0, length: bytesWritten)
|
||||
self.dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
|
||||
self._countOfBytesSent += bytesWritten
|
||||
completionHandler(nil)
|
||||
} else {
|
||||
self._error = outputStream.streamError
|
||||
completionHandler(outputStream.streamError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -captureStreams completes any already enqueued reads
|
||||
* and writes, and then invokes the
|
||||
* URLSession:streamTask:didBecomeInputStream:outputStream: delegate
|
||||
* message. When that message is received, the task object is
|
||||
* considered completed and will not receive any more delegate
|
||||
* messages. */
|
||||
public func captureStreams() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.captureStreams()
|
||||
} else {
|
||||
guard let outputStream = outputStream, let inputStream = inputStream else {
|
||||
return
|
||||
}
|
||||
dispatch_async(dispatch_queue) {
|
||||
self.write(false)
|
||||
while inputStream.streamStatus != .AtEnd {
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
self.streamDelegate?.URLSession?(self._underlyingSession, streamTask: self, didBecomeInputStream: inputStream, outputStream: outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Enqueue a request to close the write end of the underlying socket.
|
||||
* All outstanding IO will complete before the write side of the
|
||||
* socket is closed. The server, however, may continue to write bytes
|
||||
* back to the client, so best practice is to continue reading from
|
||||
* the server until you receive EOF.
|
||||
*/
|
||||
public func closeWrite() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.closeWrite()
|
||||
} else {
|
||||
dispatch_async(dispatch_queue, {
|
||||
self.write(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func write(close: Bool) {
|
||||
guard let outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
while self.dataToBeSent.length > 0 {
|
||||
let bytesWritten = outputStream.write(UnsafePointer(self.dataToBeSent.bytes), maxLength: self.dataToBeSent.length) ?? -1
|
||||
if bytesWritten > 0 {
|
||||
let range = NSRange(location: 0, length: bytesWritten)
|
||||
self.dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
|
||||
self._countOfBytesSent += bytesWritten
|
||||
} else {
|
||||
self._error = outputStream.streamError
|
||||
}
|
||||
if self.dataToBeSent.length == 0 {
|
||||
break
|
||||
}
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
if close {
|
||||
outputStream.close()
|
||||
self.streamDelegate?.URLSession?(self._underlyingSession, writeClosedForStreamTask: self)
|
||||
}
|
||||
}
|
||||
|
||||
/* Enqueue a request to close the read side of the underlying socket.
|
||||
* All outstanding IO will complete before the read side is closed.
|
||||
* You may continue writing to the server.
|
||||
*/
|
||||
public func closeRead() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.closeRead()
|
||||
} else {
|
||||
guard let inputStream = inputStream else {
|
||||
return
|
||||
}
|
||||
dispatch_async(dispatch_queue) {
|
||||
while inputStream.streamStatus != .AtEnd {
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
inputStream.close()
|
||||
self.streamDelegate?.URLSession?(self._underlyingSession, readClosedForStreamTask: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Begin encrypted handshake. The hanshake begins after all pending
|
||||
* IO has completed. TLS authentication callbacks are sent to the
|
||||
* session's -URLSession:task:didReceiveChallenge:completionHandler:
|
||||
*/
|
||||
public func startSecureConnection() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.startSecureConnection()
|
||||
} else {
|
||||
inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
|
||||
outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanly close a secure connection after all pending secure IO has
|
||||
* completed.
|
||||
*/
|
||||
public func stopSecureConnection() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.stopSecureConnection()
|
||||
} else {
|
||||
inputStream!.setProperty(NSStreamSocketSecurityLevelNone, forKey: NSStreamSocketSecurityLevelKey)
|
||||
outputStream!.setProperty(NSStreamSocketSecurityLevelNone, forKey: NSStreamSocketSecurityLevelKey)
|
||||
}
|
||||
}
|
||||
|
||||
public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
|
||||
switch (eventCode) {
|
||||
case NSStreamEvent.ErrorOccurred:
|
||||
self._error = aStream.streamError
|
||||
streamDelegate?.URLSession?(_underlyingSession, task: self, didCompleteWithError: error)
|
||||
case NSStreamEvent.EndEncountered:
|
||||
break
|
||||
case NSStreamEvent.None:
|
||||
break
|
||||
case NSStreamEvent.OpenCompleted:
|
||||
break
|
||||
case NSStreamEvent.HasBytesAvailable:
|
||||
var buffer = [UInt8](count: 2048, repeatedValue: 0)
|
||||
if (aStream == inputStream) {
|
||||
while (inputStream!.hasBytesAvailable ?? false) {
|
||||
let len = inputStream!.read(&buffer, maxLength: buffer.count)
|
||||
if len > 0 {
|
||||
dataReceived.appendBytes(&buffer, length: len)
|
||||
self._countOfBytesRecieved += len
|
||||
}
|
||||
}
|
||||
}
|
||||
case NSStreamEvent.HasSpaceAvailable:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSURLSession {
|
||||
/* Creates a bidirectional stream task to a given host and port.
|
||||
*/
|
||||
public func fpstreamTaskWithHostName(hostname: String, port: Int) -> FPSStreamTask {
|
||||
return FPSStreamTask(session: self, host: hostname, port: port)
|
||||
}
|
||||
|
||||
/* Creates a bidirectional stream task with an NSNetService to identify the endpoint.
|
||||
* The NSNetService will be resolved before any IO completes.
|
||||
*/
|
||||
public func fpstreamTaskWithNetService(service: NSNetService) -> FPSStreamTask {
|
||||
return fpstreamTaskWithNetService(service)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public protocol FPSStreamDelegate : NSURLSessionTaskDelegate {
|
||||
|
||||
/* Indiciates that the read side of a connection has been closed. Any
|
||||
* outstanding reads complete, but future reads will immediately fail.
|
||||
* This may be sent even when no reads are in progress. However, when
|
||||
* this delegate message is received, there may still be bytes
|
||||
* available. You only know that no more bytes are available when you
|
||||
* are able to read until EOF. */
|
||||
optional func URLSession(session: NSURLSession, readClosedForStreamTask streamTask: FPSStreamTask)
|
||||
|
||||
/* Indiciates that the write side of a connection has been closed.
|
||||
* Any outstanding writes complete, but future writes will immediately
|
||||
* fail.
|
||||
*/
|
||||
optional func URLSession(session: NSURLSession, writeClosedForStreamTask streamTask: FPSStreamTask)
|
||||
|
||||
/* A notification that the system has determined that a better route
|
||||
* to the host has been detected (eg, a wi-fi interface becoming
|
||||
* available.) This is a hint to the delegate that it may be
|
||||
* desirable to create a new task for subsequent work. Note that
|
||||
* there is no guarantee that the future task will be able to connect
|
||||
* to the host, so callers should should be prepared for failure of
|
||||
* reads and writes over any new interface. */
|
||||
optional func URLSession(session: NSURLSession, betterRouteDiscoveredForStreamTask streamTask: FPSStreamTask)
|
||||
|
||||
/* The given task has been completed, and unopened NSInputStream and
|
||||
* NSOutputStream objects are created from the underlying network
|
||||
* connection. This will only be invoked after all enqueued IO has
|
||||
* completed (including any necessary handshakes.) The streamTask
|
||||
* will not receive any further delegate messages.
|
||||
*/
|
||||
optional func URLSession(session: NSURLSession, streamTask: FPSStreamTask, didBecomeInputStream inputStream: NSInputStream, outputStream: NSOutputStream)
|
||||
}
|
||||
|
||||
private let ports = ["http": 80,
|
||||
"https": 443,
|
||||
"smb": 445,
|
||||
"ftp": 21,
|
||||
"sftp": 22,
|
||||
"sftp": 2121,
|
||||
"telnet": 23,
|
||||
"pop": 110,
|
||||
"smtp": 25,
|
||||
"imap": 143]
|
||||
private let securePorts = ["https": 443,
|
||||
"smb": 445,
|
||||
"sftp": 22,
|
||||
"sftp": 2121,
|
||||
"telnet": 992,
|
||||
"pop": 995,
|
||||
"smtp": 465,
|
||||
"imap": 993]
|
||||
@@ -72,7 +72,7 @@ public class FileObject {
|
||||
public let isHidden: Bool
|
||||
public let isReadOnly: Bool
|
||||
|
||||
public init(absoluteURL: NSURL?, name: String, path: String, size: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
|
||||
public init(absoluteURL: NSURL? = nil, name: String, path: String, size: Int64 = -1, createdDate: NSDate? = nil, modifiedDate: NSDate? = nil, fileType: FileType = .Regular, isHidden: Bool = false, isReadOnly: Bool = false) {
|
||||
self.absoluteURL = absoluteURL
|
||||
self.name = name
|
||||
self.path = path
|
||||
@@ -84,18 +84,6 @@ public class FileObject {
|
||||
self.isReadOnly = isReadOnly
|
||||
}
|
||||
|
||||
public init(name: String, path: String, createdDate: NSDate?, modifiedDate: NSDate?, isHidden: Bool, isReadOnly: Bool) {
|
||||
self.absoluteURL = nil
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.size = -1
|
||||
self.createdDate = createdDate
|
||||
self.modifiedDate = modifiedDate
|
||||
self.fileType = .Regular
|
||||
self.isHidden = isHidden
|
||||
self.isReadOnly = isReadOnly
|
||||
}
|
||||
|
||||
public var isDirectory: Bool {
|
||||
return self.fileType == .Directory
|
||||
}
|
||||
@@ -122,6 +110,8 @@ public protocol FileProviderBasic: class {
|
||||
*/
|
||||
func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void))
|
||||
func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void))
|
||||
|
||||
func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void))
|
||||
}
|
||||
|
||||
public protocol FileProviderOperations: FileProviderBasic {
|
||||
@@ -187,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 {
|
||||
@@ -336,7 +330,7 @@ public protocol FileOperationDelegate: class {
|
||||
/// fileProvider(_:shouldOperate:) gives the delegate an opportunity to filter the file operation. Returning true from this method will allow the copy to happen. Returning false from this method causes the item in question to be skipped. If the item skipped was a directory, no children of that directory will be subject of the operation, nor will the delegate be notified of those children.
|
||||
func fileProvider(fileProvider: FileProviderOperations, shouldDoOperation operation: FileOperation) -> Bool
|
||||
|
||||
/// fileProvider:shouldProceedAfterError:copyingItemAtPath:toPath: gives the delegate an opportunity to recover from or continue copying after an error. If an error occurs, the error object will contain an ErrorType indicating the problem. The source path and destination paths are also provided. If this method returns true, the FileProvider instance will continue as if the error had not occurred. If this method returns false, the NSFileManager instance will stop copying, return false from copyItemAtPath:toPath:error: and the error will be provied there.
|
||||
/// fileProvider(_:shouldProceedAfterError:copyingItemAtPath:toPath:) gives the delegate an opportunity to recover from or continue copying after an error. If an error occurs, the error object will contain an ErrorType indicating the problem. The source path and destination paths are also provided. If this method returns true, the FileProvider instance will continue as if the error had not occurred. If this method returns false, the NSFileManager instance will stop copying, return false from copyItemAtPath:toPath:error: and the error will be provied there.
|
||||
func fileProvider(fileProvider: FileProviderOperations, shouldProceedAfterError error: ErrorType, operation: FileOperation) -> Bool
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,13 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
return fileAttr
|
||||
}
|
||||
|
||||
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
|
||||
let dict = (try? NSFileManager.defaultManager().attributesOfFileSystemForPath(baseURL?.path ?? "/")) as NSDictionary?;
|
||||
let totalSize = dict?.objectForKey(NSFileSystemSize)?.longLongValue ?? -1;
|
||||
let freeSize = dict?.objectForKey(NSFileSystemFreeSize)?.longLongValue ?? 0;
|
||||
completionHandler(total: totalSize, used: totalSize - freeSize)
|
||||
}
|
||||
|
||||
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
|
||||
dispatch_async(dispatch_queue) {
|
||||
completionHandler(attributes: self.attributesOfItemAtURL(self.absoluteURL(path)), error: nil)
|
||||
@@ -324,6 +331,7 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
for (i, monitor) in monitors.enumerate() {
|
||||
if self.relativePathOf(url: monitor.url) == path {
|
||||
removedMonitor = monitors.removeAtIndex(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
removedMonitor?.stop()
|
||||
|
||||
+86
-29
@@ -31,26 +31,83 @@ internal func decode<T>(data: NSData) -> T {
|
||||
// This client implementation is for little-endian platform, namely x86, x64 & arm
|
||||
// For big-endian platforms like PowerPC, there must be a huge overhaul
|
||||
|
||||
class SMBProtocolClient: TCPSocketClient {
|
||||
protocol SMBProtocolClientDelegate: class {
|
||||
func receivedSMB2Response(header: SMB2.Header, response: SMBResponse)
|
||||
}
|
||||
|
||||
class SMB2ProtocolClient: FPSStreamTask {
|
||||
var currentMessageID: UInt64 = 0
|
||||
var sessionId: UInt64 = 0
|
||||
|
||||
func negotiateToSMB2() -> SMB2.NegotiateResponse? {
|
||||
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: []))
|
||||
SMBProtocolClient.createSMB2Message(smbHeader, message: negMessage)
|
||||
do {
|
||||
try self.send(data: nil)
|
||||
} catch _ {
|
||||
return nil
|
||||
}
|
||||
self.waitUntilResponse()
|
||||
let response = try? SMBProtocolClient.digestSMB2Message(dataReceived)
|
||||
return response??.message as? SMB2.NegotiateResponse
|
||||
weak var delegate: SMBProtocolClientDelegate?
|
||||
|
||||
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
|
||||
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 {
|
||||
@@ -62,14 +119,14 @@ class SMBProtocolClient: TCPSocketClient {
|
||||
|
||||
// MARK: create and analyse messages
|
||||
|
||||
class func determineSMBVersion(data: NSData) -> Float {
|
||||
func determineSMBVersion(data: NSData) -> Float {
|
||||
var smbverChar: Int8 = 0
|
||||
data.getBytes(&smbverChar, length: 1)
|
||||
let version = 0 - smbverChar
|
||||
return Float(version)
|
||||
}
|
||||
|
||||
class func digestSMBMessage(data: NSData) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: NSData?)]) {
|
||||
func digestSMBMessage(data: NSData) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: NSData?)]) {
|
||||
guard data.length > 30 else {
|
||||
throw NSURLError.BadServerResponse
|
||||
}
|
||||
@@ -106,7 +163,7 @@ class SMBProtocolClient: TCPSocketClient {
|
||||
return (header, blocks)
|
||||
}
|
||||
|
||||
class func digestSMB2Message(data: NSData) throws -> (header: SMB2.Header, message: SMBResponse?)? {
|
||||
func digestSMB2Message(data: NSData) throws -> (header: SMB2.Header, message: SMBResponse?)? {
|
||||
guard data.length > 65 else {
|
||||
throw NSURLError.BadServerResponse
|
||||
}
|
||||
@@ -136,25 +193,25 @@ class SMBProtocolClient: TCPSocketClient {
|
||||
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:
|
||||
return (header, SMB2.Echo(data: messageData))
|
||||
case .QUERY_DIRECTORY:
|
||||
return (header, nil) // FIXME:
|
||||
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:
|
||||
@@ -162,7 +219,7 @@ class SMBProtocolClient: TCPSocketClient {
|
||||
}
|
||||
}
|
||||
|
||||
class func createSMBMessage(header: SMB1.Header, blocks: [(params: NSData?, message: NSData?)]) -> NSData {
|
||||
func createSMBMessage(header: SMB1.Header, blocks: [(params: NSData?, message: NSData?)]) -> NSData {
|
||||
var headerv = header
|
||||
let result = NSMutableData(data: encode(&headerv))
|
||||
for block in blocks {
|
||||
@@ -180,7 +237,7 @@ class SMBProtocolClient: TCPSocketClient {
|
||||
return result
|
||||
}
|
||||
|
||||
class func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> NSData {
|
||||
func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> NSData {
|
||||
var headerv = header
|
||||
let result = NSMutableData(data: encode(&headerv))
|
||||
result.appendData(message.data())
|
||||
|
||||
@@ -40,6 +40,10 @@ public class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
public weak var fileOperationDelegate: FileOperationDelegate?
|
||||
|
||||
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
|
||||
@@ -16,23 +16,24 @@ protocol SMBResponse {
|
||||
init? (data: NSData)
|
||||
}
|
||||
|
||||
|
||||
protocol IOCtlRequestProtocol: SMBRequest {}
|
||||
protocol IOCtlResponseProtocol: SMBResponse {}
|
||||
|
||||
|
||||
struct SMBTime {
|
||||
var time: UInt64
|
||||
var time: Int64
|
||||
|
||||
init(time: UInt64) {
|
||||
init(time: Int64) {
|
||||
self.time = time
|
||||
}
|
||||
|
||||
init(unixTime: UInt) {
|
||||
self.time = (UInt64(unixTime) + 11644473600) * 10000000
|
||||
self.time = (Int64(unixTime) + 11644473600) * 10000000
|
||||
}
|
||||
|
||||
init(timeIntervalSince1970: NSTimeInterval) {
|
||||
self.time = UInt64((timeIntervalSince1970 + 11644473600) * 10000000)
|
||||
self.time = Int64((timeIntervalSince1970 + 11644473600) * 10000000)
|
||||
}
|
||||
|
||||
init(date: NSDate) {
|
||||
|
||||
@@ -26,7 +26,7 @@ extension SMB2 {
|
||||
var header = self.header
|
||||
var offset = 0x78 //UInt16(sizeof(SMB2.Header.self) + sizeof(CreateContext.Header.self) - 1)
|
||||
let body = NSMutableData()
|
||||
if let name = self.name, let nameData = name.dataUsingEncoding(NSUTF8StringEncoding) {
|
||||
if let name = self.name, let nameData = name.dataUsingEncoding(NSUTF16StringEncoding) {
|
||||
header.nameOffset = UInt16(offset)
|
||||
header.nameLength = UInt16(nameData.length)
|
||||
offset += nameData.length
|
||||
@@ -226,7 +226,7 @@ extension SMB2 {
|
||||
let buffer: NSData
|
||||
|
||||
init(name: ContextNames, data: NSData) {
|
||||
let nameData = NSMutableData(data: (name.rawValue).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
let nameData = NSMutableData(data: (name.rawValue).dataUsingEncoding(NSUTF16StringEncoding)!)
|
||||
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.length), reserved: 0, dataOffset: UInt16(nameData.length), dataLength: UInt32(data.length))
|
||||
self.buffer = data
|
||||
}
|
||||
|
||||
@@ -247,5 +247,4 @@ extension SMB2 {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -243,11 +243,12 @@ extension SMB2 {
|
||||
// SRV_ENUMERATE_SNAPSHOTS
|
||||
struct SrvSnapshots: IOCtlResponseProtocol {
|
||||
let count: UInt32
|
||||
let returnedCount: UInt32
|
||||
let snapshots: [SMBTime]
|
||||
|
||||
init?(data: NSData) {
|
||||
self.count = decode(data)
|
||||
let returnedCount: UInt32 = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
|
||||
self.returnedCount = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
|
||||
//let size: UInt32 = decode(data.subdataWithRange(NSRange(location: 8, length: 4)))
|
||||
var snapshots = [SMBTime]()
|
||||
let dateFormatter = NSDateFormatter()
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// SMB2Notification.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian on 5/18/95.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Change Notify
|
||||
|
||||
struct ChangeNotifyRequest: SMBRequest {
|
||||
let size: UInt16
|
||||
let flags: ChangeNotifyRequest.Flags
|
||||
let outputBufferLength: UInt32
|
||||
let fileId: FileId
|
||||
let completionFilters: CompletionFilter
|
||||
private let reserved: UInt32
|
||||
|
||||
init(fileId: FileId, completionFilters: CompletionFilter, flags: ChangeNotifyRequest.Flags = [], outputBufferLength: UInt32 = 65535) {
|
||||
self.size = 32
|
||||
self.flags = flags
|
||||
self.outputBufferLength = outputBufferLength
|
||||
self.fileId = fileId
|
||||
self.completionFilters = completionFilters
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
func data() -> NSData {
|
||||
return encode(self)
|
||||
}
|
||||
|
||||
struct Flags: OptionSetType {
|
||||
let rawValue: UInt16
|
||||
|
||||
init(rawValue: UInt16) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let WATCH_TREE = Flags(rawValue: 0x0001)
|
||||
}
|
||||
|
||||
struct CompletionFilter: OptionSetType {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// The client is notified if a file-name changes.
|
||||
static let FILE_NAME = CompletionFilter(rawValue: 0x00000001)
|
||||
/// The client is notified if a directory name changes.
|
||||
static let DIR_NAME = CompletionFilter(rawValue: 0x00000002)
|
||||
/// The client is notified if a file's attributes change.
|
||||
static let ATTRIBUTES = CompletionFilter(rawValue: 0x00000004)
|
||||
/// The client is notified if a file's size changes.
|
||||
static let SIZE = CompletionFilter(rawValue: 0x00000008)
|
||||
/// The client is notified if the last write time of a file changes.
|
||||
static let LAST_WRITE = CompletionFilter(rawValue: 0x00000010)
|
||||
/// The client is notified if the last access time of a file changes.
|
||||
static let LAST_ACCESS = CompletionFilter(rawValue: 0x00000020)
|
||||
/// The client is notified if the creation time of a file changes.
|
||||
static let CREATION = CompletionFilter(rawValue: 0x00000040)
|
||||
/// The client is notified if a file's extended attributes (EAs) change.
|
||||
static let EA = CompletionFilter(rawValue: 0x00000080)
|
||||
/// The client is notified of a file's access control list (ACL) settings change.
|
||||
static let SECURITY = CompletionFilter(rawValue: 0x00000100)
|
||||
/// The client is notified if a named stream is added to a file.
|
||||
static let STREAM_NAME = CompletionFilter(rawValue: 0x00000200)
|
||||
/// The client is notified if the size of a named stream is changed.
|
||||
static let STREAM_SIZE = CompletionFilter(rawValue: 0x00000400)
|
||||
/// The client is notified if a named stream is modified.
|
||||
static let STREAM_WRITE = Flags(rawValue: 0x00000800)
|
||||
|
||||
static let all = CompletionFilter(rawValue: 0x00000FFF)
|
||||
static let list: CompletionFilter = [.FILE_NAME, .DIR_NAME]
|
||||
}
|
||||
}
|
||||
|
||||
struct ChangeNotifyResponse: SMBResponse {
|
||||
let notifications: [(action: FileNotifyAction, fileName: String)]
|
||||
|
||||
init?(data: NSData) {
|
||||
let maxLoop = 1000
|
||||
var i = 0
|
||||
var result = [(action: FileNotifyAction, fileName: String)]()
|
||||
|
||||
var offset: UInt32 = 0
|
||||
while i < maxLoop {
|
||||
let actionData = data.subdataWithRange(NSRange(location: Int(offset + 4), length: 4))
|
||||
let actionValue: UInt32 = decode(actionData)
|
||||
guard let action = FileNotifyAction(rawValue: actionValue) else {
|
||||
continue
|
||||
}
|
||||
let fileLenData = data.subdataWithRange(NSRange(location: Int(offset + 8), length: 4))
|
||||
let fileNameLen: UInt32 = decode(fileLenData)
|
||||
let fileNameData = data.subdataWithRange(NSRange(location: Int(offset + 12), length: Int(12 + fileNameLen)))
|
||||
let fileName = String(data: fileNameData, encoding: NSUTF16StringEncoding) ?? ""
|
||||
result.append((action: action, fileName: fileName))
|
||||
|
||||
let nextOffsetData = data.subdataWithRange(NSRange(location: Int(offset), length: 4))
|
||||
let nextOffset: UInt32 = decode(nextOffsetData)
|
||||
offset += nextOffset
|
||||
if nextOffset == 0 {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
self.notifications = result
|
||||
}
|
||||
}
|
||||
|
||||
enum FileNotifyAction: UInt32 {
|
||||
/// The file was added to the directory.
|
||||
case ADDED = 0x00000001
|
||||
/// The file was removed from the directory.
|
||||
case REMOVED = 0x00000002
|
||||
/// The file was modified. This can be a change to the data or attributes of the file.
|
||||
case MODIFIED = 0x00000003
|
||||
/// The file was renamed, and this is the old name. If the new name resides outside of the directory being monitored, the client will not receive the FILE_ACTION_RENAMED_NEW_NAME bit value.
|
||||
case RENAMED_OLD_NAME = 0x00000004
|
||||
/// The file was renamed, and this is the new name. If the old name resides outside of the directory being monitored, the client will not receive the FILE_ACTION_RENAME_OLD_NAME bit value.
|
||||
case RENAMED_NEW_NAME = 0x00000005
|
||||
/// The file was added to a named stream.
|
||||
case ADDED_STREAM = 0x00000006
|
||||
/// The file was removed from the named stream.
|
||||
case REMOVED_STREAM = 0x00000007
|
||||
/// The file was modified. This can be a change to the data or attributes of the file.
|
||||
case MODIFIED_STREAM = 0x00000008
|
||||
/// An object ID was removed because the file the object ID referred to was deleted. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
|
||||
case REMOVED_BY_DELETE = 0x00000009
|
||||
/// An attempt to tunnel object ID information to a file being created or renamed failed because the object ID is in use by another file on the same volume. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
|
||||
case NOT_TUNNELLED = 0x0000000A
|
||||
/// An attempt to tunnel object ID information to a file being renamed failed because the file already has an object ID. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
|
||||
case TUNNELLED_ID_COLLISION = 0x0000000B
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,329 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Query Directory
|
||||
|
||||
// MARK: SMB2 Change Notify
|
||||
struct QueryDirectoryRequest: SMBRequest {
|
||||
let header: QueryDirectoryRequest.Header
|
||||
let searchPattern: String?
|
||||
|
||||
/// - **bufferLength:** maximum number of bytes the server is allowed to return which is the same as maxTransactSize returned by negotiation.
|
||||
/// - **searchPattern:** can hold wildcards or be nil if all entries should be returned.
|
||||
/// - **fileIndex:** The byte offset within the directory, indicating the position at which to resume the enumeration.
|
||||
init(fileId: FileId, infoClass: FileInformationEnum, flags: Flags, bufferLength: UInt32 = 65535, searchPattern: String? = nil, fileIndex: UInt32 = 0) {
|
||||
assert(FileInformationEnum.queryDirectory.contains(infoClass), "Invalid FileInformationClass used for QueryDirectoryRequest")
|
||||
let searchPatternOffset = searchPattern != nil ? sizeof(SMB2.Header.self) + sizeof(QueryDirectoryRequest.Header.self) : 0
|
||||
let nflags = flags.intersect(fileIndex > 0 ? [.INDEX_SPECIFIED] : [])
|
||||
let searchPatternLength = searchPattern?.dataUsingEncoding(NSUTF16StringEncoding)?.length ?? 0
|
||||
self.header = Header(size: 53, infoClass: infoClass, flags: nflags, fileIndex: fileIndex, fileId: fileId, searchPatternOffset: UInt8(searchPatternOffset), searchPatternLength: UInt8(searchPatternLength), bufferLength: bufferLength)
|
||||
self.searchPattern = searchPattern
|
||||
}
|
||||
|
||||
func data() -> NSData {
|
||||
let result = NSMutableData(data: encode(header))
|
||||
if let patternData = searchPattern?.dataUsingEncoding(NSUTF16StringEncoding) {
|
||||
result.appendData(patternData)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
struct Header {
|
||||
let size: UInt8
|
||||
let infoClass: FileInformationEnum
|
||||
let flags: QueryDirectoryRequest.Flags
|
||||
let fileIndex: UInt32
|
||||
let fileId: FileId
|
||||
let searchPatternOffset: UInt8
|
||||
let searchPatternLength: UInt8
|
||||
let bufferLength: UInt32
|
||||
}
|
||||
|
||||
struct Flags: OptionSetType {
|
||||
let rawValue: UInt8
|
||||
|
||||
init(rawValue: UInt8) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let RESTART_SCANS = Flags(rawValue: 0x01)
|
||||
static let RETURN_SINGLE_ENTRY = Flags(rawValue: 0x02)
|
||||
static let INDEX_SPECIFIED = Flags(rawValue: 0x04)
|
||||
static let REOPEN = Flags(rawValue: 0x10)
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryDirectoryResponse: SMBResponse {
|
||||
let buffer: NSData
|
||||
|
||||
func parseAs(type type: FileInformationEnum) -> [(header: SMB2FilesInformationHeader, fileName: String)] {
|
||||
var offset = 0
|
||||
var result = [(header: SMB2FilesInformationHeader, fileName: String)]()
|
||||
while true {
|
||||
let header: SMB2FilesInformationHeader
|
||||
switch type {
|
||||
case .FileDirectoryInformation:
|
||||
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileDirectoryInformationHeader)))
|
||||
let h: FileDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
case .FileFullDirectoryInformation:
|
||||
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileFullDirectoryInformationHeader)))
|
||||
let h: FileFullDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
case .FileIdFullDirectoryInformation:
|
||||
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileIdFullDirectoryInformationHeader)))
|
||||
let h: FileIdFullDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
case .FileBothDirectoryInformation:
|
||||
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileBothDirectoryInformationHeader)))
|
||||
let h: FileBothDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
case .FileIdBothDirectoryInformation:
|
||||
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileIdBothDirectoryInformationHeader)))
|
||||
let h: FileIdBothDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
case .FileNamesInformation:
|
||||
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileNamesInformationHeader)))
|
||||
let h: FileNamesInformationHeader = decode(headerData)
|
||||
header = h
|
||||
default:
|
||||
return []
|
||||
}
|
||||
let fnData = buffer.subdataWithRange(NSRange(location: offset + sizeofValue(header), length: Int(header.fileNameLength)))
|
||||
let fileName = String(data: fnData, usingEncoding: NSUTF16StringEncoding)
|
||||
result.append((header: header, fileName: fileName))
|
||||
if header.nextEntryOffset == 0 {
|
||||
break
|
||||
}
|
||||
offset += Int(header.nextEntryOffset)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
init? (data: NSData) {
|
||||
let offset: UInt16 = decode(data.subdataWithRange(NSRange(location: 2, length: 2)))
|
||||
let length: UInt32 = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
|
||||
guard data.length > Int(offset) + Int(length) else {
|
||||
return nil
|
||||
}
|
||||
self.buffer = data.subdataWithRange(NSRange(location: Int(offset), length: Int(length)))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SMB2 Query Info
|
||||
|
||||
struct QueryInfoRequest: SMBRequest {
|
||||
let header: Header
|
||||
let buffer: NSData?
|
||||
|
||||
init(fileId: FileId, infoClass: FileInformationEnum, outputBufferLength: UInt32 = 65535) {
|
||||
self.header = Header(size: 41, infoType: 1, infoClass: infoClass.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: [], flags: [], fileId: fileId)
|
||||
self.buffer = nil
|
||||
}
|
||||
|
||||
init(fileId: FileId, extendedAttributes: [String], flags: Flags = [], outputBufferLength: UInt32 = 65535) {
|
||||
let buffer = NSMutableData()
|
||||
for ea in extendedAttributes {
|
||||
let strData = ea.dataUsingEncoding(NSASCIIStringEncoding)!
|
||||
let strLength = UInt8(strData.length)
|
||||
let nextOffset = UInt32(4 + 1 + strData.length)
|
||||
let data = encode(nextOffset).mutableCopy() as! NSMutableData
|
||||
data.appendData(encode(strLength))
|
||||
data.appendData(strData)
|
||||
data.length += 1
|
||||
let padSize = (data.length) % 4
|
||||
data.length += padSize
|
||||
buffer.appendData(data)
|
||||
}
|
||||
|
||||
let bufferOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(QueryInfoRequest.Header.self))
|
||||
self.header = Header(size: 41, infoType: 1, infoClass: FileInformationEnum.FileFullEaInformation.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: bufferOffset, reserved: 0, inputBufferLength: UInt32(buffer.length), additionalInformation: [], flags: flags, fileId: fileId)
|
||||
self.buffer = buffer
|
||||
}
|
||||
|
||||
init(fileId: FileId, infoClass: FileSystemInformationEnum, outputBufferLength: UInt32 = 65535) {
|
||||
self.header = Header(size: 41, infoType: 2, infoClass: infoClass.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: [], flags: [], fileId: fileId)
|
||||
self.buffer = nil
|
||||
}
|
||||
|
||||
init(fileId: FileId, securityInfo: FileSecurityInfo, outputBufferLength: UInt32 = 65535) {
|
||||
self.header = Header(size: 41, infoType: 3, infoClass: 0, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: securityInfo, flags: [], fileId: fileId)
|
||||
self.buffer = nil
|
||||
}
|
||||
|
||||
// TODO: Implement QUOTA_INFO init
|
||||
|
||||
func data() -> NSData {
|
||||
let headerData = encode(header)
|
||||
let result = NSMutableData(data: headerData)
|
||||
if let buffer = buffer {
|
||||
result.appendData(buffer)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
struct Header {
|
||||
let size: UInt16
|
||||
let infoType: UInt8
|
||||
let infoClass: UInt8
|
||||
let outputBufferLength: UInt32
|
||||
let inputBufferOffset: UInt16
|
||||
private let reserved: UInt16
|
||||
let inputBufferLength: UInt32
|
||||
let additionalInformation: FileSecurityInfo
|
||||
let flags: QueryInfoRequest.Flags
|
||||
let fileId: FileId
|
||||
}
|
||||
|
||||
struct Flags: OptionSetType {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let RESTART_SCAN = Flags(rawValue: 0x00000001)
|
||||
static let RETURN_SINGLE_ENTRY = Flags(rawValue: 0x00000002)
|
||||
static let INDEX_SPECIFIED = Flags(rawValue: 0x00000004)
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryInfoResponse: SMBResponse {
|
||||
let buffer: NSData
|
||||
|
||||
init?(data: NSData) {
|
||||
let structSizeData = data.subdataWithRange(NSRange(location: 0, length: 2))
|
||||
let structSize: UInt16 = decode(structSizeData)
|
||||
guard structSize == 9 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*let offsetData = data.subdataWithRange(NSRange(location: 2, length: 2))
|
||||
let offset: UInt16 = decode(offsetData)*/
|
||||
|
||||
let lengthData = data.subdataWithRange(NSRange(location: 4, length: 4))
|
||||
let length: UInt32 = decode(lengthData)
|
||||
|
||||
guard data.length >= 8 + Int(length) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.buffer = data.subdataWithRange(NSRange(location: 8, length: Int(length)))
|
||||
}
|
||||
|
||||
var asAccessInformation: FileAccessInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asAlignmentInformation: FileAlignmentInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asAllInformation: (header: FileAllInformationHeader, name: String) {
|
||||
let header: FileAllInformationHeader = decode(buffer)
|
||||
let nameData = buffer.subdataWithRange(NSRange(location: sizeof(FileAllInformationHeader), length: Int(header.nameLength)))
|
||||
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
|
||||
return (header, name)
|
||||
}
|
||||
|
||||
var asAlternateNameInformation: String {
|
||||
let b = UnsafePointer<CChar>(buffer.bytes)
|
||||
return String(CString: b, encoding: NSUTF16StringEncoding) ?? ""
|
||||
}
|
||||
|
||||
var asAttributeTagInformation: FileAttributeTagInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asBasicInformation: FileBasicInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asCompressionInformation: FileCompressionInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asEaInformation: FileEaInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asFullEaInformation: FileFullEaInformation {
|
||||
// TODO:
|
||||
return FileFullEaInformation()
|
||||
}
|
||||
|
||||
var asInternalInformation: FileInternalInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asModeInformation: FileModeInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asNetworkOpenInformation: FileNetworkOpenInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asPipeInformation: FilePipeInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asPipeLocalInformation: FilePipeLocalInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asPipeRemoteInformation: FilePipeRemoteInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asPositionInformation: FilePositionInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asStandardInformation: FileStandardInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asStreamInformation: (header: FileStreamInformationHeader, name: String) {
|
||||
let header: FileStreamInformationHeader = decode(buffer)
|
||||
let nameData = buffer.subdataWithRange(NSRange(location: sizeof(FileStreamInformationHeader), length: Int(header.streamNameLength)))
|
||||
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
|
||||
return (header, name)
|
||||
}
|
||||
|
||||
var asFsVolumeInformation: (header: FileFsVolumeInformationHeader, name: String) {
|
||||
let header: FileFsVolumeInformationHeader = decode(buffer)
|
||||
let nameData = buffer.subdataWithRange(NSRange(location: sizeof(FileFsVolumeInformationHeader), length: Int(header.labelLength)))
|
||||
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
|
||||
return (header, name)
|
||||
}
|
||||
|
||||
var asFsSizeInformation: FileFsSizeInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asFsDeviceInformation: FileFsDeviceInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asFsAttributeInformation: (header: FileFsAttributeInformationHeader, name: String) {
|
||||
let header: FileFsAttributeInformationHeader = decode(buffer)
|
||||
let nameData = buffer.subdataWithRange(NSRange(location: sizeof(FileFsAttributeInformationHeader), length: Int(header.nameLength)))
|
||||
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
|
||||
return (header, name)
|
||||
}
|
||||
|
||||
var asFsControlInformation: FileFsControlInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asFsFullSizeInformation: FileFsFullSizeInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asFsObjectIdInformation: FileFsObjectIdInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
|
||||
var asFsSectorSizeInformation: FileFsSectorSizeInformation {
|
||||
return decode(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,623 @@
|
||||
//
|
||||
// SMB2QueryTypes.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian on 5/19/95.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol SMB2FilesInformationHeader: SMBResponse {
|
||||
var nextEntryOffset: UInt32 { get }
|
||||
var fileIndex: UInt32 { get }
|
||||
var fileNameLength : UInt32 { get }
|
||||
}
|
||||
|
||||
extension SMB2 {
|
||||
enum FileInformationEnum: UInt8 {
|
||||
case Nil = 0x00
|
||||
case FileDirectoryInformation = 0x01
|
||||
case FileFullDirectoryInformation = 0x02
|
||||
case FileBothDirectoryInformation = 0x03
|
||||
case FileBasicInformation = 0x04
|
||||
case FileStandardInformation = 0x05
|
||||
case FileInternalInformation = 0x06
|
||||
case FileEaInformation = 0x07
|
||||
case FileAccessInformation = 0x08
|
||||
case FileNameInformation = 0x09
|
||||
case FileRenameInformation = 0x0A
|
||||
case FileLinkInformation = 0x0B
|
||||
case FileNamesInformation = 0x0C
|
||||
case FileDispositionInformation = 0x0D
|
||||
case FilePositionInformation = 0x0E
|
||||
case FileFullEaInformation = 0x0F
|
||||
case FileModeInformation = 0x10
|
||||
case FileAlignmentInformation = 0x11
|
||||
case FileAllInformation = 0x12
|
||||
case FileAllocationInformation = 0x13
|
||||
case FileEndOfFileInformation = 0x14
|
||||
case FileAlternateNameInformation = 0x15
|
||||
case FileStreamInformation = 0x16
|
||||
case FilePipeInformation = 0x17
|
||||
case FilePipeLocalInformation = 0x18
|
||||
case FilePipeRemoteInformation = 0x19
|
||||
case FileMailslotQueryInformation = 0x1A
|
||||
case FileMailslotSetInformation = 0x1B
|
||||
case FileCompressionInformation = 0x1C
|
||||
case FileObjectIdInformation = 0x1D
|
||||
case FileCompletionInformation = 0x1E
|
||||
case FileMoveClusterInformation = 0x1F
|
||||
case FileQuotaInformation = 0x20
|
||||
case FileReparsePointInformation = 0x21
|
||||
case FileNetworkOpenInformation = 0x22
|
||||
case FileAttributeTagInformation = 0x23
|
||||
case FileTrackingInformation = 0x24
|
||||
case FileIdBothDirectoryInformation = 0x25
|
||||
case FileIdFullDirectoryInformation = 0x26
|
||||
case FileValidDataLengthInformation = 0x27
|
||||
case FileShortNameInformation = 0x28
|
||||
case FileIoCompletionNotificationInformation = 0x29
|
||||
case FileIoStatusBlockRangeInformation = 0x2A
|
||||
case FileIoPriorityHintInformation = 0x2B
|
||||
case FileSfioReserveInformation = 0x2C
|
||||
case FileSfioVolumeInformation = 0x2D
|
||||
case FileHardLinkInformation = 0x2E
|
||||
case FileProcessIdsUsingFileInformation = 0x2F
|
||||
case FileNormalizedNameInformation = 0x30
|
||||
case FileNetworkPhysicalNameInformation = 0x31
|
||||
case FileIdGlobalTxDirectoryInformation = 0x32
|
||||
case FileIsRemoteDeviceInformation = 0x33
|
||||
case FileUnusedInformation = 0x34
|
||||
case FileNumaNodeInformation = 0x35
|
||||
case FileStandardLinkInformation = 0x36
|
||||
case FileRemoteProtocolInformation = 0x37
|
||||
case FileRenameInformationBypassAccessCheck = 0x38
|
||||
case FileLinkInformationBypassAccessCheck = 0x39
|
||||
case FileVolumeNameInformation = 0x3A
|
||||
case FileIdInformation = 0x3B
|
||||
case FileIdExtdDirectoryInformation = 0x3C
|
||||
case FileReplaceCompletionInformation = 0x3D
|
||||
case FileHardLinkFullIdInformation = 0x3E
|
||||
case FileIdExtdBothDirectoryInformation = 0x3F
|
||||
case FileMaximumInformation = 0x40
|
||||
|
||||
static let queryDirectory: [FileInformationEnum] = [.FileDirectoryInformation, .FileFullDirectoryInformation, .FileIdFullDirectoryInformation, .FileBothDirectoryInformation, .FileIdBothDirectoryInformation, .FileNamesInformation]
|
||||
|
||||
static let queryInfoFile: [FileInformationEnum] = [.FileAccessInformation, .FileAlignmentInformation, .FileAllInformation, .FileAlternateNameInformation, .FileAttributeTagInformation, .FileBasicInformation, .FileCompressionInformation, FileEaInformation, .FileFullEaInformation, .FileInternalInformation, .FileModeInformation, .FileNetworkOpenInformation, .FilePipeInformation, .FilePipeLocalInformation, .FilePipeRemoteInformation, .FilePositionInformation, .FileStandardInformation, .FileStreamInformation]
|
||||
}
|
||||
|
||||
enum FileSystemInformationEnum: UInt8 {
|
||||
case Nil = 0
|
||||
case FileFsAttributeInformation
|
||||
case FileFsControlInformation
|
||||
case FileFsDeviceInformation
|
||||
case FileFsFullSizeInformation
|
||||
case FileFsObjectIdInformation
|
||||
case FileFsSectorSizeInformation
|
||||
case FileFsSizeInformation
|
||||
case FileFsVolumeInformation
|
||||
}
|
||||
|
||||
struct FileSecurityInfo: OptionSetType {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let OWNER = FileSecurityInfo(rawValue: 0x00000001)
|
||||
static let GROUP = FileSecurityInfo(rawValue: 0x00000002)
|
||||
static let DACL = FileSecurityInfo(rawValue: 0x00000004)
|
||||
static let SACL = FileSecurityInfo(rawValue: 0x00000008)
|
||||
static let LABEL = FileSecurityInfo(rawValue: 0x00000010)
|
||||
static let ATTRIBUTE = FileSecurityInfo(rawValue: 0x00000020)
|
||||
static let SCOPE = FileSecurityInfo(rawValue: 0x00000040)
|
||||
static let BACKUP = FileSecurityInfo(rawValue: 0x00010000)
|
||||
}
|
||||
|
||||
struct FileDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: UInt64
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
|
||||
init?(data: NSData) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFullDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: UInt64
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
|
||||
init?(data: NSData) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileIdFullDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: UInt64
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
private let reserved: UInt32
|
||||
let fileId: FileId
|
||||
|
||||
init?(data: NSData) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileBothDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: UInt64
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
private let shortNameLen: UInt8
|
||||
private let reserved: UInt8
|
||||
private let _shortName: FileShortNameType
|
||||
var shortName: String? {
|
||||
let s = encode(_shortName)
|
||||
let d = NSMutableData(data: s)
|
||||
d.length = Int(shortNameLen)
|
||||
return String(data: d, encoding: NSUTF16StringEncoding)
|
||||
}
|
||||
|
||||
init?(data: NSData) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileIdBothDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: Int64
|
||||
let allocationSize: Int64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
private let shortNameLen: UInt8
|
||||
private let reserved: UInt8
|
||||
private let _shortName: FileShortNameType
|
||||
var shortName: String? {
|
||||
let s = encode(_shortName)
|
||||
let d = NSMutableData(data: s)
|
||||
d.length = Int(shortNameLen)
|
||||
return String(data: d, encoding: NSUTF16StringEncoding)
|
||||
}
|
||||
private let reserved2: UInt16
|
||||
let fileId : FileId
|
||||
|
||||
init?(data: NSData) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileNamesInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let fileNameLength : UInt32
|
||||
|
||||
init?(data: NSData) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
typealias FileShortNameType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
|
||||
|
||||
struct FileAccessInformation {
|
||||
let accessMask: FileAccessMask
|
||||
}
|
||||
|
||||
struct FileAlignmentInformation {
|
||||
private let _alignment: UInt32
|
||||
var alignmentLength: UInt32 {
|
||||
return _alignment + 1
|
||||
}
|
||||
}
|
||||
|
||||
struct FileAllInformationHeader {
|
||||
let basic: FileBasicInformation
|
||||
let standard: FileStandardInformation
|
||||
let `internal`: FileInternalInformation
|
||||
let ea: FileEaInformation
|
||||
let access: FileAccessInformation
|
||||
let position: FilePositionInformation
|
||||
let mode: FileModeInformation
|
||||
let alignment: FileAlignmentInformation
|
||||
let nameLength: UInt32
|
||||
}
|
||||
|
||||
struct FileAttributeTagInformation {
|
||||
let fileAttributes: FileAttributes
|
||||
let reparseTag: UInt32
|
||||
}
|
||||
|
||||
struct FileBasicInformation {
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileAttributes: FileAttributes
|
||||
private let reserved: UInt32 = 0
|
||||
}
|
||||
|
||||
struct FileCompressionInformation {
|
||||
let compressedFileSize: Int64
|
||||
let compressionFormat: UInt16
|
||||
static let COMPRESSION_FORMAT_LZNT1 = 0x0002
|
||||
let compressionUnitShift: UInt8
|
||||
let chunkShift: UInt8
|
||||
let clusterShift: UInt8
|
||||
private let reserved: (UInt8, UInt16)
|
||||
}
|
||||
|
||||
struct FileEaInformation {
|
||||
let eaSize: UInt32
|
||||
}
|
||||
|
||||
struct FileFullEaInformation {
|
||||
// TODO
|
||||
}
|
||||
|
||||
struct FileInternalInformation {
|
||||
let indexNumber: UInt64
|
||||
}
|
||||
|
||||
struct FileModeInformation {
|
||||
let mode: Mode
|
||||
|
||||
struct Mode: OptionSetType {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let FILE_WRITE_THROUGH = Mode(rawValue: 0x00000002)
|
||||
static let FILE_SEQUENTIAL_ONLY = Mode(rawValue: 0x00000004)
|
||||
static let FILE_NO_INTERMEDIATE_BUFFERING = Mode(rawValue: 0x00000008)
|
||||
static let FILE_SYNCHRONOUS_IO_ALERT = Mode(rawValue: 0x00000010)
|
||||
static let FILE_SYNCHRONOUS_IO_NONALERT = Mode(rawValue: 0x00000020)
|
||||
static let FILE_DELETE_ON_CLOSE = Mode(rawValue: 0x00001000)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileNetworkOpenInformation {
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileAttributes: FileAttributes
|
||||
private let reserved: UInt32
|
||||
}
|
||||
|
||||
struct FilePipeInformation {
|
||||
private let _readMode: UInt32
|
||||
var readMode: ReadMode {
|
||||
return ReadMode(rawValue: _readMode) ?? .BYTE_STREAM_MODE
|
||||
}
|
||||
private let _completionMode: UInt32
|
||||
var completionMode: CompletionMode {
|
||||
return CompletionMode(rawValue: _completionMode) ?? .QUEUE_OPERATION
|
||||
}
|
||||
|
||||
enum ReadMode: UInt32 {
|
||||
case BYTE_STREAM_MODE = 0x00000000
|
||||
case MESSAGE_MODE = 0x00000001
|
||||
}
|
||||
|
||||
enum CompletionMode: UInt32 {
|
||||
case QUEUE_OPERATION = 0x00000000
|
||||
case COMPLETE_OPERATION = 0x00000001
|
||||
}
|
||||
}
|
||||
|
||||
struct FilePipeLocalInformation {
|
||||
private let _namedPipeType: UInt32
|
||||
var namedPipeType: Type {
|
||||
return Type(rawValue: _namedPipeType) ?? .BYTE_STREAM_TYPE
|
||||
}
|
||||
private let _namedPipeConfiguration: UInt32
|
||||
var namedPipeConfiguration: Configuration {
|
||||
return Configuration(rawValue: _namedPipeConfiguration) ?? .INBOUND
|
||||
}
|
||||
let maximumInstances: UInt32
|
||||
let currentInstances: UInt32
|
||||
let inboundQuota: UInt32
|
||||
let readDataAvailable: UInt32
|
||||
let outboundQuota: UInt32
|
||||
let writeQuotaAvailable: UInt32
|
||||
private let _namedPipeState: UInt32
|
||||
var namedPipeState: State {
|
||||
return State(rawValue: _namedPipeState) ?? .DISCONNECTED_STATE
|
||||
}
|
||||
private let _namedPipeEnd: UInt32
|
||||
var namedPipeEnd: End {
|
||||
return End(rawValue: _namedPipeEnd) ?? .CLIENT_END
|
||||
}
|
||||
|
||||
enum Type: UInt32 {
|
||||
case BYTE_STREAM_TYPE = 0x00000000
|
||||
case MESSAGE_TYPE = 0x00000001
|
||||
}
|
||||
|
||||
enum Configuration: UInt32 {
|
||||
case INBOUND = 0x00000000
|
||||
case OUTBOUND = 0x00000001
|
||||
case FULL_DUPLEX = 0x00000002
|
||||
}
|
||||
|
||||
enum State: UInt32 {
|
||||
case DISCONNECTED_STATE = 0x00000001
|
||||
case LISTENING_STATE = 0x00000002
|
||||
case CONNECTED_STATE = 0x00000003
|
||||
case CLOSING_STATE = 0x00000004
|
||||
}
|
||||
|
||||
enum End: UInt32 {
|
||||
case CLIENT_END = 0x00000000
|
||||
case SERVER_END = 0x00000001
|
||||
}
|
||||
}
|
||||
|
||||
struct FilePipeRemoteInformation {
|
||||
let collectDataTime: SMBTime
|
||||
let maximumCollectionCount: UInt32
|
||||
}
|
||||
|
||||
struct FilePositionInformation {
|
||||
let currentByteOffset: Int64
|
||||
}
|
||||
|
||||
struct FileStandardInformation {
|
||||
let allocationSize: Int64
|
||||
let fileSize: Int64
|
||||
let numberOfLinks: UInt32
|
||||
let deletePending: Bool
|
||||
let directory: Bool
|
||||
private let reserved: UInt16
|
||||
}
|
||||
|
||||
struct FileStreamInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let streamNameLength: UInt32
|
||||
let streamSize: Int64
|
||||
let streamAllocationSize: Int64
|
||||
}
|
||||
|
||||
struct FileFsVolumeInformationHeader {
|
||||
let creationTime: SMBTime
|
||||
let serial: UInt32
|
||||
let labelLength: UInt32
|
||||
let supportObjects: Bool
|
||||
let reserved: UInt8
|
||||
}
|
||||
|
||||
struct FileFsSizeInformation {
|
||||
let totalAllocationUnits: Int64
|
||||
let availableAllocationUnits: Int64
|
||||
let sectorsPerAllocationUnit: UInt32
|
||||
let bytesPerSector: UInt32
|
||||
}
|
||||
|
||||
struct FileFsDeviceInformation {
|
||||
private let _deviceType: UInt32
|
||||
var deviceType: DeviceType {
|
||||
return DeviceType(rawValue: _deviceType) ?? .DISK
|
||||
}
|
||||
let charactristics: Charactristics
|
||||
|
||||
enum DeviceType: UInt32 {
|
||||
case CD_ROM = 0x00000002
|
||||
case DISK = 0x00000007
|
||||
}
|
||||
|
||||
struct Charactristics: OptionSetType {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// Storage device supports removable media. For example, drivers for JAZ drive devices specify this characteristic, but drivers for PCMCIA flash disks do not.
|
||||
static let REMOVABLE_MEDIA = Charactristics(rawValue: 0x00000001)
|
||||
/// Indicates that the device cannot be written to.
|
||||
static let READ_ONLY_DEVICE = Charactristics(rawValue: 0x00000002)
|
||||
/// Indicates that the device is a floppy disk device.
|
||||
static let FLOPPY_DISKETTE = Charactristics(rawValue: 0x00000004)
|
||||
/// Indicates that the device supports write-once media.
|
||||
static let WRITE_ONCE_MEDIA = Charactristics(rawValue: 0x00000008)
|
||||
/// ndicates that the volume is for a remote file system like SMB or CIFS.
|
||||
static let REMOTE_DEVICE = Charactristics(rawValue: 0x00000010)
|
||||
/// Indicates that a file system is mounted on the device.
|
||||
static let DEVICE_IS_MOUNTED = Charactristics(rawValue: 0x00000020)
|
||||
/// Indicates that the volume does not directly reside on storage media, but resides on some other type of media (memory for example).
|
||||
static let VIRTUAL_VOLUME = Charactristics(rawValue: 0x00000040)
|
||||
/// By default, volumes do not check the ACL associated with the volume, but instead use the ACLs associated with individual files on the volume. When this flag is set the volume ACL is also checked.
|
||||
static let DEVICE_SECURE_OPEN = Charactristics(rawValue: 0x00000100)
|
||||
/// Indicates that the device object is part of a Terminal Services device stack.
|
||||
static let TS_DEVICE = Charactristics(rawValue: 0x00001000)
|
||||
/// ndicates that a web-based Distributed Authoring and Versioning (WebDAV) file system is mounted on the device.
|
||||
static let WEBDAV_DEVICE = Charactristics(rawValue: 0x00002000)
|
||||
/// The IO Manager normally performs a full security check for traverse access on every file open when the client is an appcontainer. Setting of this flag bypasses this enforced traverse access check if the client token already has traverse privileges.
|
||||
static let PORTABLE_DEVICE = Charactristics(rawValue: 0x0004000)
|
||||
/// Indicates that the given device resides on a portable bus like USB or Firewire and that the entire device (not just the media) can be removed from the system.
|
||||
static let DEVICE_ALLOW_APPCONTAINER_TRAVERSAL = Charactristics(rawValue: 0x00020000)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFsAttributeInformationHeader {
|
||||
let attributes: Attributes
|
||||
let maximumFileNameLength: Int32
|
||||
let nameLength: UInt32
|
||||
|
||||
struct Attributes: OptionSetType {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// The file system supports case-sensitive file names when looking up (searching for) file names in a directory.
|
||||
static let CASE_SENSITIVE_SEARCH = Attributes(rawValue: 0x00000001)
|
||||
/// The file system preserves the case of file names when it places a name on disk.
|
||||
static let CASE_PRESERVED_NAMES = Attributes(rawValue: 0x00000002)
|
||||
/// The file system supports Unicode in file and directory names. This flag applies only to file and directory names; the file system neither restricts nor interprets the bytes of data within a file.
|
||||
static let UNICODE_ON_DISK = Attributes(rawValue: 0x00000004)
|
||||
/// The file system preserves and enforces access control lists (ACLs).
|
||||
static let PERSISTENT_ACLS = Attributes(rawValue: 0x00000008)
|
||||
/// The file volume supports file-based compression. This flag is incompatible with the FILE_VOLUME_IS_COMPRESSED flag.
|
||||
static let FILE_COMPRESSION = Attributes(rawValue: 0x00000010)
|
||||
/// The file system supports per-user quotas.
|
||||
static let VOLUME_QUOTAS = Attributes(rawValue: 0x00000020)
|
||||
/// The file system supports sparse files.
|
||||
static let SUPPORTS_SPARSE_FILES = Attributes(rawValue: 0x00000040)
|
||||
/// The file system supports reparse points.
|
||||
static let SUPPORTS_REPARSE_POINTS = Attributes(rawValue: 0x00000080)
|
||||
/// The file system supports remote storage.
|
||||
static let REMOTE_STORAGE = Attributes(rawValue: 0x00000100)
|
||||
/// The specified volume is a compressed volume. This flag is incompatible with the FILE_FILE_COMPRESSION flag.
|
||||
static let IS_COMPRESSED = Attributes(rawValue: 0x00008000)
|
||||
/// The file system supports object identifiers.
|
||||
static let OBJECT_IDS = Attributes(rawValue: 0x00010000)
|
||||
/// The file system supports the Encrypted File System (EFS).
|
||||
static let ENCRYPTION = Attributes(rawValue: 0x00020000)
|
||||
/// The file system supports named streams. (aka. Resource Fork on MacOS)
|
||||
static let NAMED_STREAMS = Attributes(rawValue: 0x00040000)
|
||||
/// If set, the volume has been mounted in read-only mode.
|
||||
static let READ_ONLY_VOLUME = Attributes(rawValue: 0x00080000)
|
||||
/// The underlying volume is write once. (aka tapes)
|
||||
static let SEQUENTIAL_WRITE_ONCE = Attributes(rawValue: 0x00100000)
|
||||
/// The volume supports transactions.
|
||||
static let SUPPORTS_TRANSACTIONS = Attributes(rawValue: 0x00200000)
|
||||
/// The file system supports hard linking files.
|
||||
static let SUPPORTS_HARD_LINKS = Attributes(rawValue: 0x00400000)
|
||||
/// The file system persistently stores Extended Attribute information per file.
|
||||
static let SUPPORTS_EXTENDED_ATTRIBUTES = Attributes(rawValue: 0x00800000)
|
||||
/// The file system supports opening a file by FileID or ObjectID.
|
||||
static let SUPPORTS_OPEN_BY_FILE_ID = Attributes(rawValue: 0x01000000)
|
||||
/// The file system implements a USN change journal.
|
||||
static let USN_JOURNAL = Attributes(rawValue: 0x02000000)
|
||||
/// The file system supports integrity streams.
|
||||
static let SUPPORT_INTEGRITY_STREAMS = Attributes(rawValue: 0x04000000)
|
||||
/// The file system supports sharing logical clusters between files on the same volume. The file system reallocates on writes to shared clusters. Indicates that FSCTL_DUPLICATE_EXTENTS_TO_FILE is a supported operation.
|
||||
static let SUPPORTS_BLOCK_REFCOUNTING = Attributes(rawValue: 0x08000000)
|
||||
/// The file system tracks whether each cluster of a file contains valid data (either from explicit file writes or automatic zeros) or invalid data (has not yet been written to or zeroed).
|
||||
static let SUPPORTS_SPARSE_VDL = Attributes(rawValue: 0x10000000)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFsControlInformation {
|
||||
let freeSpaceStartFiltering: Int64
|
||||
let freeSpaceThreshold: Int64
|
||||
let freeSpaceStopFiltering: Int64
|
||||
let defaultQuotaThreshold: UInt64
|
||||
let defaultQuotaLimit: UInt64
|
||||
let flags: Flags
|
||||
private let padding: UInt32 = 0
|
||||
|
||||
struct Flags: OptionSetType {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// Quotas are tracked on the volume, but they are not enforced. Tracked quotas enable reporting on the file system space used by system users. If both this flag and FILE_VC_QUOTA_ENFORCE are specified, FILE_VC_QUOTA_ENFORCE is ignored.
|
||||
static let QUOTA_TRACK = Flags(rawValue: 0x00000001)
|
||||
/// Quotas are tracked and enforced on the volume.
|
||||
static let QUOTA_ENFORCE = Flags(rawValue: 0x00000002)
|
||||
/// Content indexing is disabled.
|
||||
static let CONTENT_INDEX_DISABLED = Flags(rawValue: 0x00000008)
|
||||
/// An event log entry will be created when the user exceeds his or her assigned quota warning threshold.
|
||||
static let LOG_QUOTA_THRESHOLD = Flags(rawValue: 0x00000010)
|
||||
/// An event log entry will be created when the user exceeds the assigned disk quota limit.
|
||||
static let LOG_QUOTA_LIMIT = Flags(rawValue: 0x00000020)
|
||||
/// An event log entry will be created when the volume's free space threshold is exceeded.
|
||||
static let LOG_VOLUME_THRESHOLD = Flags(rawValue: 0x00000040)
|
||||
/// An event log entry will be created when the volume's free space limit is exceeded.
|
||||
static let LOG_VOLUME_LIMIT = Flags(rawValue: 0x00000080)
|
||||
/// The quota information for the volume is incomplete because it is corrupt, or the system is in the process of rebuilding the quota information.
|
||||
static let QUOTAS_INCOMPLETE = Flags(rawValue: 0x00000100)
|
||||
/// The file system is rebuilding the quota information for the volume.
|
||||
static let QUOTAS_REBUILDING = Flags(rawValue: 0x00000200)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFsFullSizeInformation {
|
||||
let totalAllocationUnits: Int64
|
||||
let callerAvailableAllocationUnits: Int64
|
||||
let actualAvailableAllocationUnits: Int64
|
||||
let sectorsPerAllocationUnit: UInt32
|
||||
let bytesPerSector: UInt32
|
||||
}
|
||||
|
||||
struct FileFsObjectIdInformation {
|
||||
let objectId: uuid_t
|
||||
let extendedInfo: (UInt64, UInt64, UInt64, UInt64, UInt64, UInt64)
|
||||
}
|
||||
|
||||
struct FileFsSectorSizeInformation {
|
||||
let logicalBytesPerSector: UInt32
|
||||
let physicalBytesPerSectorForAtomicity: UInt32
|
||||
let physicalBytesPerSectorForPerformance: UInt32
|
||||
let effectivePhysicalBytesPerSectorForAtomicity: UInt32
|
||||
let flags: Flags
|
||||
let byteOffsetForSectorAlignment: UInt32
|
||||
let byteOffsetForPartitionAlignment: UInt32
|
||||
|
||||
struct Flags: OptionSetType {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// When set, this flag indicates that the first physical sector of the device is aligned with the first logical sector. When not set, the first physical sector of the device is misaligned with the first logical sector.
|
||||
static let ALIGNED_DEVICE = Flags(rawValue: 0x00000001)
|
||||
/// When set, this flag indicates that the partition is aligned to physical sector boundaries on the storage device.
|
||||
static let PARTITION_ALIGNED_ON_DEVICE = Flags(rawValue: 0x00000002)
|
||||
/// When set, the device reports that it does not incur a seek penalty (this typically indicates that the device does not have rotating media, such as flash-based disks).
|
||||
static let NO_SEEK_PENALTY = Flags(rawValue: 0x00000008)
|
||||
/// When set, the device supports TRIM operations, either T13 (ATA) TRIM or T10 (SCSI/SAS) UNMAP.
|
||||
static let TRIM_ENABLED = Flags(rawValue: 0x00000010)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -60,17 +66,17 @@ extension SMB2 {
|
||||
var contextCount: UInt16
|
||||
private let reserved2: UInt16
|
||||
var clientStartTime: SMBTime {
|
||||
let time = UInt64(contextOffset) + (UInt64(contextCount) << 32) + (UInt64(contextCount) << 48)
|
||||
let time = Int64(contextOffset) + (Int64(contextCount) << 32) + (Int64(contextCount) << 48)
|
||||
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
|
||||
|
||||
@@ -10,5 +10,37 @@ import Foundation
|
||||
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Set Info
|
||||
struct SetInfoRequest: SMBRequest {
|
||||
let header: Header
|
||||
let buffer: NSData?
|
||||
|
||||
|
||||
|
||||
func data() -> NSData {
|
||||
return NSData()
|
||||
}
|
||||
|
||||
struct Header {
|
||||
let size: UInt16 = 33
|
||||
let infoType: UInt8
|
||||
private let infoClass: UInt8
|
||||
let bufferLength: UInt32
|
||||
let bufferOffset: UInt16
|
||||
private let reserved: UInt16
|
||||
let securityInfo: FileSecurityInfo
|
||||
let fileId: FileId
|
||||
}
|
||||
}
|
||||
|
||||
struct SetInfoResponse: SMBResponse {
|
||||
let size: UInt16
|
||||
|
||||
init() {
|
||||
self.size = 2
|
||||
}
|
||||
|
||||
init? (data: NSData) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,12 @@ 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
|
||||
let path = "\\\\\(host)\\\(share)"
|
||||
self.buffer = path.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
self.buffer = path.dataUsingEncoding(NSUTF16StringEncoding)
|
||||
}
|
||||
|
||||
func data() -> NSData {
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
//
|
||||
// SocketTransmitter.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class TCPSocketClient: NSObject, NSStreamDelegate {
|
||||
public static let ports = ["http": 80,
|
||||
"https": 443,
|
||||
"smb": 445,
|
||||
"ftp": 21,
|
||||
"sftp": 22,
|
||||
"sftp": 2121,
|
||||
"telnet": 23,
|
||||
"pop": 110,
|
||||
"smtp": 25,
|
||||
"imap": 143]
|
||||
public static let securePorts = ["https": 443,
|
||||
"smb": 445,
|
||||
"sftp": 22,
|
||||
"sftp": 2121,
|
||||
"telnet": 992,
|
||||
"pop": 995,
|
||||
"smtp": 465,
|
||||
"imap": 993]
|
||||
|
||||
private var inputStream: NSInputStream?
|
||||
private var outputStream: NSOutputStream?
|
||||
private var dataToBeSent: NSMutableData = NSMutableData()
|
||||
/// holds data received from server
|
||||
public let dataReceived: NSMutableData = NSMutableData()
|
||||
/// a url with valid scheme, dns or ip host and ports path and query sections will be neglected
|
||||
public let baseURL: NSURL
|
||||
/// a url with valid scheme, dns or ip host and ports path and query sections will be neglected
|
||||
public let secureConnection: Bool
|
||||
/// server's ports which is value between 1 to 65535
|
||||
private let port: UInt32
|
||||
private var open = false
|
||||
|
||||
/**
|
||||
* - parameter baseURL: a url with valid scheme, dns or ip host and ports
|
||||
* path and query sections will be neglected
|
||||
*
|
||||
* **Note** Call `connect()` to establish connection
|
||||
* - parameter secure: establishing connection using an SSL/TLS connection
|
||||
*/
|
||||
|
||||
public init?(baseURL: NSURL, secure: Bool = false) {
|
||||
self.baseURL = baseURL
|
||||
self.secureConnection = secure
|
||||
let scheme = baseURL.uw_scheme.lowercaseString
|
||||
let defaultPort = secure ? UInt32(TCPSocketClient.securePorts[scheme] ?? 0) : UInt32(TCPSocketClient.ports[scheme] ?? 0)
|
||||
self.port = baseURL.port?.unsignedIntValue ?? defaultPort
|
||||
if self.port == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
disconnect()
|
||||
}
|
||||
|
||||
/**
|
||||
* Establshes a connection to desired server
|
||||
* - returns: A bool value which indicated there where no system error during
|
||||
* creating connection
|
||||
*/
|
||||
|
||||
public func connect() -> Bool {
|
||||
guard let hostStr = baseURL.host else {
|
||||
return false
|
||||
}
|
||||
var readStream : Unmanaged<CFReadStream>?
|
||||
var writeStream : Unmanaged<CFWriteStream>?
|
||||
let host : CFString = NSString(string: hostStr)
|
||||
|
||||
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, self.port, &readStream, &writeStream)
|
||||
inputStream = readStream?.takeRetainedValue()
|
||||
outputStream = writeStream?.takeRetainedValue()
|
||||
|
||||
guard let inputStream = inputStream, outputStream = outputStream else {
|
||||
return false
|
||||
}
|
||||
|
||||
inputStream.delegate = self
|
||||
outputStream.delegate = self
|
||||
|
||||
inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
|
||||
if secureConnection {
|
||||
inputStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
|
||||
outputStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
|
||||
}
|
||||
|
||||
inputStream.open()
|
||||
outputStream.open()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates connection to the server
|
||||
*/
|
||||
|
||||
public func disconnect() {
|
||||
inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
|
||||
outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
|
||||
|
||||
self.inputStream?.close()
|
||||
self.outputStream?.close()
|
||||
|
||||
self.inputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
self.outputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
|
||||
self.inputStream?.delegate = nil
|
||||
self.outputStream?.delegate = nil
|
||||
|
||||
self.inputStream = nil
|
||||
self.outputStream = nil
|
||||
}
|
||||
|
||||
public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
|
||||
switch (eventCode) {
|
||||
case NSStreamEvent.ErrorOccurred:
|
||||
open = false
|
||||
case NSStreamEvent.EndEncountered:
|
||||
break
|
||||
case NSStreamEvent.None:
|
||||
break
|
||||
case NSStreamEvent.OpenCompleted:
|
||||
let activeStatus: [NSStreamStatus] = [.Open, .Reading, .Writing, .AtEnd]
|
||||
open = activeStatus.contains(inputStream?.streamStatus ?? .NotOpen) && activeStatus.contains(outputStream?.streamStatus ?? .NotOpen)
|
||||
case NSStreamEvent.HasBytesAvailable:
|
||||
var buffer = [UInt8](count: 2048, repeatedValue: 0)
|
||||
if ( aStream == inputStream) {
|
||||
while (inputStream!.hasBytesAvailable ?? false) {
|
||||
let len = inputStream!.read(&buffer, maxLength: buffer.count)
|
||||
if len > 0 {
|
||||
dataReceived.appendBytes(&buffer, length: len)
|
||||
}
|
||||
}
|
||||
}
|
||||
case NSStreamEvent.HasSpaceAvailable:
|
||||
if aStream == outputStream {
|
||||
do {
|
||||
try send(data: nil)
|
||||
} catch _ {
|
||||
NSLog("Sending error")
|
||||
}
|
||||
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data to server
|
||||
* - parameter data: data which is intended to be sent to server
|
||||
* - throws: NSURLError.NetworkConnectionLost in case of server disconnects disgracefully
|
||||
*/
|
||||
|
||||
public func send(data data: NSData?) throws {
|
||||
guard let outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
if outputStream.hasSpaceAvailable ?? false {
|
||||
if let data = data {
|
||||
dataToBeSent.appendData(data)
|
||||
}
|
||||
|
||||
if dataToBeSent.length > 0 {
|
||||
let bytesWritten = outputStream.write(UnsafePointer(dataToBeSent.bytes), maxLength: dataToBeSent.length) ?? -1
|
||||
if bytesWritten > 0 {
|
||||
let range = NSRange(location: 0, length: bytesWritten)
|
||||
dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
|
||||
} else {
|
||||
throw NSError(domain: NSURLErrorDomain, code: NSURLError.NetworkConnectionLost.rawValue, userInfo: nil)
|
||||
}
|
||||
}
|
||||
//println("Sent the following")
|
||||
} else { //steam busy
|
||||
if let data = data {
|
||||
dataToBeSent.appendData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears entire send and receive buffer
|
||||
*/
|
||||
|
||||
public func flush() {
|
||||
dataToBeSent.length = 0
|
||||
dataReceived.length = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Put's thread in sleep until all data is sent
|
||||
* **Note:** Don't call this method from main thread
|
||||
*/
|
||||
|
||||
internal func waitUntillDataSent() {
|
||||
if NSThread.isMainThread() {
|
||||
assert(false, "waitUntillDataSent() method can't be called from main thread")
|
||||
}
|
||||
while true {
|
||||
if dataToBeSent.length == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put's thread in sleep until all response from server is loaded into tcp stack
|
||||
* server response can be retrieved by `dataReceived` property
|
||||
* **Note:** Don't call this method from main thread
|
||||
* - returns: A Bool value indicates all response loaded from server successfullt
|
||||
*/
|
||||
|
||||
internal func waitUntilResponse() -> Bool {
|
||||
if NSThread.isMainThread() {
|
||||
assert(false, "waitUntilResponse() method can't be called from main thread")
|
||||
}
|
||||
var finished = false
|
||||
while !finished {
|
||||
switch inputStream?.streamStatus ?? .Error {
|
||||
case .AtEnd:
|
||||
finished = true
|
||||
return true
|
||||
case .Closed, .Error:
|
||||
return false
|
||||
default:
|
||||
finished = false
|
||||
}
|
||||
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -8,45 +8,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum FileProviderWebDavErrorCode: Int {
|
||||
case OK = 200
|
||||
case Created = 201
|
||||
case NoContent = 204
|
||||
case MultiStatus = 207
|
||||
case Forbidden = 403
|
||||
case MethodNotAllowed = 405
|
||||
case Conflict = 409
|
||||
case PreconditionFailed = 412
|
||||
case UnsupportedMediaType = 415
|
||||
case Locked = 423
|
||||
case FailedDependency = 424
|
||||
case BadGateway = 502
|
||||
case InsufficientStorage = 507
|
||||
}
|
||||
|
||||
public struct FileProviderWebDavError: ErrorType, CustomStringConvertible {
|
||||
public let code: FileProviderWebDavErrorCode
|
||||
public let url: NSURL
|
||||
|
||||
public var description: String {
|
||||
switch code {
|
||||
case .OK: return "OK"
|
||||
case .Created: return "Created"
|
||||
case .NoContent: return "No Content"
|
||||
case .MultiStatus: return ""
|
||||
case .Forbidden: return "Forbidden"
|
||||
case .MethodNotAllowed: return "Method Not Allowed"
|
||||
case .Conflict: return "Conflict"
|
||||
case .PreconditionFailed: return "Precondition Failed"
|
||||
case .UnsupportedMediaType: return "Unsupported Media Type"
|
||||
case .Locked: return "Locked"
|
||||
case .FailedDependency: return "Failed Dependency"
|
||||
case .BadGateway: return "Bad Gateway"
|
||||
case .InsufficientStorage: return "Insufficient Storage"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class WebDavFileObject: FileObject {
|
||||
public let contentType: String
|
||||
public let entryTag: String?
|
||||
@@ -102,12 +63,15 @@ public class WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
let url = absoluteURL(path)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "PROPFIND"
|
||||
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
|
||||
request.setValue("1", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.HTTPBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".dataUsingEncoding(NSUTF8StringEncoding)
|
||||
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
if let data = data {
|
||||
let xresponse = self.parseXMLResponse(data)
|
||||
var fileObjects = [WebDavFileObject]()
|
||||
@@ -117,31 +81,63 @@ public class WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
}
|
||||
fileObjects.append(self.mapToFileObject(attr))
|
||||
}
|
||||
completionHandler(contents: fileObjects, error: error)
|
||||
completionHandler(contents: fileObjects, error: responseError ?? error)
|
||||
return
|
||||
}
|
||||
completionHandler(contents: [], error: error)
|
||||
completionHandler(contents: [], error: responseError ?? error)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
|
||||
let request = NSMutableURLRequest(URL: absoluteURL(path))
|
||||
let url = absoluteURL(path)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "PROPFIND"
|
||||
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
|
||||
request.setValue("1", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.HTTPBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".dataUsingEncoding(NSUTF8StringEncoding)
|
||||
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
if let data = data {
|
||||
let xresponse = self.parseXMLResponse(data)
|
||||
if let attr = xresponse.first {
|
||||
completionHandler(attributes: self.mapToFileObject(attr), error: error)
|
||||
completionHandler(attributes: self.mapToFileObject(attr), error: responseError ?? error)
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(attributes: nil, error: error)
|
||||
completionHandler(attributes: nil, error: responseError ?? error)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
|
||||
// Not all WebDAV clients implements RFC2518 which allows geting storage quota.
|
||||
// In this case you won't get error. totalSize is NSURLSessionTransferSizeUnknown
|
||||
// and used space is zero.
|
||||
guard let baseURL = baseURL else {
|
||||
return
|
||||
}
|
||||
let request = NSMutableURLRequest(URL: baseURL)
|
||||
request.HTTPMethod = "PROPFIND"
|
||||
request.setValue("0", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.HTTPBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop><D:quota-available-bytes/><D:quota-used-bytes/></D:prop>\n</D:propfind>".dataUsingEncoding(NSUTF8StringEncoding)
|
||||
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
if let data = data {
|
||||
let xresponse = self.parseXMLResponse(data)
|
||||
if let attr = xresponse.first {
|
||||
let totalSize = Int64(attr.prop["quota-available-bytes"] ?? "")
|
||||
let usedSize = Int64(attr.prop["quota-used-bytes"] ?? "")
|
||||
completionHandler(total: totalSize ?? -1, used: usedSize ?? 0)
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(total: -1, used: 0)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
@@ -154,24 +150,32 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
let url = absoluteURL((atPath as NSString).stringByAppendingPathComponent(folderName) + "/")
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "MKCOL"
|
||||
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
if let response = response as? NSHTTPURLResponse, let code = FileProviderWebDavErrorCode(rawValue: response.statusCode) where code != .OK {
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
if let response = response as? NSHTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) where code != .OK {
|
||||
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
|
||||
return
|
||||
}
|
||||
completionHandler?(error: error)
|
||||
self.delegateNotify(.Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"), error: error)
|
||||
completionHandler?(error: responseError ?? error)
|
||||
self.delegateNotify(.Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"), error: responseError ?? error)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func createFile(fileAttribs: FileObject, atPath path: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
|
||||
let request = NSMutableURLRequest(URL: absoluteURL(path))
|
||||
let url = absoluteURL(path)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "PUT"
|
||||
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
|
||||
completionHandler?(error: error)
|
||||
self.delegateNotify(.Create(path: (path as NSString).stringByAppendingPathComponent(fileAttribs.name)), error: error)
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
completionHandler?(error: responseError ?? error)
|
||||
self.delegateNotify(.Create(path: (path as NSString).stringByAppendingPathComponent(fileAttribs.name)), error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Create", "source": (path as NSString).stringByAppendingPathComponent(fileAttribs.name)])
|
||||
task.resume()
|
||||
@@ -193,13 +197,12 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
} else {
|
||||
request.HTTPMethod = "COPY"
|
||||
}
|
||||
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
|
||||
request.setValue(absoluteURL(path).uw_absoluteString, forHTTPHeaderField: "Destination")
|
||||
if !overwrite {
|
||||
request.setValue("F", forHTTPHeaderField: "Overwrite")
|
||||
}
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
if let response = response as? NSHTTPURLResponse, let code = FileProviderWebDavErrorCode(rawValue: response.statusCode) {
|
||||
if let response = response as? NSHTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
defer {
|
||||
let op = move ? FileOperation.Move(source: path, destination: toPath) : .Copy(source: path, destination: toPath)
|
||||
self.delegateNotify(op, error: error)
|
||||
@@ -223,9 +226,8 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
let url = absoluteURL(path)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "DELETE"
|
||||
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
if let response = response as? NSHTTPURLResponse, let code = FileProviderWebDavErrorCode(rawValue: response.statusCode) {
|
||||
if let response = response as? NSHTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
defer {
|
||||
self.delegateNotify(.Remove(path: path), error: error)
|
||||
}
|
||||
@@ -245,19 +247,29 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
let request = NSMutableURLRequest(URL: absoluteURL(toPath))
|
||||
let url = absoluteURL(toPath)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "PUT"
|
||||
let task = session.uploadTaskWithRequest(request, fromFile: localFile) { (data, response, error) in
|
||||
completionHandler?(error: error)
|
||||
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: error)
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
completionHandler?(error: responseError ?? error)
|
||||
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": localFile.uw_absoluteString, "dest": toPath])
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
|
||||
let request = NSMutableURLRequest(URL: absoluteURL(path))
|
||||
let url = absoluteURL(path)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
let task = session.downloadTaskWithRequest(request) { (sourceFileURL, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
if let sourceFileURL = sourceFileURL {
|
||||
do {
|
||||
try NSFileManager.defaultManager().copyItemAtURL(sourceFileURL, toURL: toLocalURL)
|
||||
@@ -266,7 +278,7 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler?(error: error)
|
||||
completionHandler?(error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.uw_absoluteString])
|
||||
task.resume()
|
||||
@@ -279,7 +291,8 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
|
||||
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
|
||||
let request = NSMutableURLRequest(URL: absoluteURL(path))
|
||||
let url = absoluteURL(path)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "GET"
|
||||
if length > 0 {
|
||||
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
|
||||
@@ -287,7 +300,11 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
|
||||
}
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
completionHandler(contents: data, error: error)
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
completionHandler(contents: data, error: responseError ?? error)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
@@ -298,16 +315,20 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "PUT"
|
||||
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: self.absoluteURL(path))
|
||||
}
|
||||
defer {
|
||||
self.delegateNotify(.Modify(path: path), error: error)
|
||||
self.delegateNotify(.Modify(path: path), error: responseError ?? error)
|
||||
}
|
||||
if let error = error {
|
||||
completionHandler?(error: error)
|
||||
return
|
||||
}
|
||||
if atomically {
|
||||
self.moveItemAtPath((path as NSString).stringByAppendingPathExtension("tmp")!, toPath: path, completionHandler: completionHandler)
|
||||
}
|
||||
if let error = error {
|
||||
// If there is no error, completionHandler has been executed by move command
|
||||
completionHandler?(error: error)
|
||||
}
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Modify", "source": path])
|
||||
task.resume()
|
||||
@@ -317,13 +338,16 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
let url = absoluteURL(path)
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "PROPFIND"
|
||||
request.setValue(baseURL?.uw_absoluteString, forHTTPHeaderField: "Host")
|
||||
//request.setValue("1", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.HTTPBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".dataUsingEncoding(NSUTF8StringEncoding)
|
||||
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
|
||||
let task = session.dataTaskWithRequest(request) { (data, response, error) in
|
||||
// FIXME: paginating results
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
if let data = data {
|
||||
let xresponse = self.parseXMLResponse(data)
|
||||
var fileObjects = [WebDavFileObject]()
|
||||
@@ -335,10 +359,10 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
fileObjects.append(fileObject)
|
||||
foundItemHandler?(fileObject)
|
||||
}
|
||||
completionHandler(files: fileObjects, error: error)
|
||||
completionHandler(files: fileObjects, error: responseError ?? error)
|
||||
return
|
||||
}
|
||||
completionHandler(files: [], error: error)
|
||||
completionHandler(files: [], error: responseError ?? error)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
@@ -357,6 +381,8 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
// TODO: implements methods for lock mechanism
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProvider {}
|
||||
|
||||
// MARK: WEBDAV XML response implementation
|
||||
|
||||
internal extension WebDAVFileProvider {
|
||||
@@ -419,7 +445,7 @@ internal extension WebDAVFileProvider {
|
||||
}
|
||||
for propItemNode in propStatNode[proptag].children ?? [] {
|
||||
propDic[propItemNode.name.componentsSeparatedByString(":").last!.lowercaseString] = propItemNode.value
|
||||
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xmlStringCompact.containsString("collection") {
|
||||
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xmlString.containsString("collection") {
|
||||
propDic["getcontenttype"] = "httpd/unix-directory"
|
||||
}
|
||||
}
|
||||
@@ -506,10 +532,114 @@ extension WebDAVFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDele
|
||||
}
|
||||
|
||||
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
|
||||
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
|
||||
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
|
||||
completionHandler(deposition, credential)
|
||||
}
|
||||
|
||||
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
|
||||
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
|
||||
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
|
||||
completionHandler(deposition, credential)
|
||||
}
|
||||
}
|
||||
|
||||
public struct FileProviderWebDavError: ErrorType, CustomStringConvertible {
|
||||
public let code: FileProviderHTTPErrorCode
|
||||
public let url: NSURL
|
||||
|
||||
public var description: String {
|
||||
return code.description
|
||||
}
|
||||
}
|
||||
|
||||
public enum FileProviderHTTPErrorCode: Int {
|
||||
case Continue = 100
|
||||
case SwitchingProtocols = 101
|
||||
case Processing = 102
|
||||
case OK = 200
|
||||
case Created = 201
|
||||
case Accepted = 202
|
||||
case NonAuthoritativeInformation = 203
|
||||
case NoContent = 204
|
||||
case ResetContent = 205
|
||||
case PartialContent = 206
|
||||
case MultiStatus = 207
|
||||
case AlreadyReported = 208
|
||||
case IMUsed = 226
|
||||
case MultipleChoices = 300
|
||||
case MovedPermanently = 301
|
||||
case Found = 302
|
||||
case SeeOther = 303
|
||||
case NotModified = 304
|
||||
case UseProxy = 305
|
||||
case SwitchProxy = 306
|
||||
case TemporaryRedirect = 307
|
||||
case PermanentRedirect = 308
|
||||
case BadRequest = 400
|
||||
case Unauthorized = 401
|
||||
case PaymentRequired = 402
|
||||
case Forbidden = 403
|
||||
case NotFound = 404
|
||||
case MethodNotAllowed = 405
|
||||
case NotAcceptable = 406
|
||||
case ProxyAuthenticationRequired = 407
|
||||
case RequestTimeout = 408
|
||||
case Conflict = 409
|
||||
case Gone = 410
|
||||
case LengthRequired = 411
|
||||
case PreconditionFailed = 412
|
||||
case PayloadTooLarge = 413
|
||||
case URITooLong = 414
|
||||
case UnsupportedMediaType = 415
|
||||
case RangeNotSatisfiable = 416
|
||||
case ExpectationFailed = 417
|
||||
case MisdirectedRequest = 421
|
||||
case UnprocessableEntity = 422
|
||||
case Locked = 423
|
||||
case FailedDependency = 424
|
||||
case UnorderedCollection = 425
|
||||
case UpgradeRequired = 426
|
||||
case PreconditionRequired = 428
|
||||
case TooManyRequests = 429
|
||||
case RequestHeaderFieldsTooLarge = 431
|
||||
case UnavailableForLegalReasons = 451
|
||||
case InternalServerError = 500
|
||||
case BadGateway = 502
|
||||
case ServiceUnavailable = 503
|
||||
case GatewayTimeout = 504
|
||||
case HTTPVersionNotSupported = 505
|
||||
case VariantlsoNegotiates = 506
|
||||
case InsufficientStorage = 507
|
||||
case LoopDetected = 508
|
||||
case BandwidthLimitExceeded = 509
|
||||
case NotExtended = 510
|
||||
case NetworkAuthenticationRequired = 511
|
||||
|
||||
private static let status1xx = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
|
||||
private static let status2xx = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
|
||||
private static let status3xx = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
|
||||
private static let status4xx = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
|
||||
private static let status5xx = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
|
||||
|
||||
public var description: String {
|
||||
switch self.rawValue {
|
||||
case 100...102: return FileProviderHTTPErrorCode.status1xx[self.rawValue]!
|
||||
case 200...208, 226: return FileProviderHTTPErrorCode.status2xx[self.rawValue]!
|
||||
case 300...308: return FileProviderHTTPErrorCode.status3xx[self.rawValue]!
|
||||
case 400...417, 421...426: fallthrough
|
||||
case 428, 429, 431, 451: return FileProviderHTTPErrorCode.status4xx[self.rawValue]!
|
||||
case 500...511: return FileProviderHTTPErrorCode.status5xx[self.rawValue]!
|
||||
default: return typeDescription
|
||||
}
|
||||
}
|
||||
|
||||
public var typeDescription: String {
|
||||
switch self.rawValue {
|
||||
case 100...199: return "Informational"
|
||||
case 200...299: return "Success"
|
||||
case 300...399: return "Redirection"
|
||||
case 400...499: return "Client Error"
|
||||
case 500...599: return "Server Error"
|
||||
default: return "Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user