Compare commits

...

39 Commits

Author SHA1 Message Date
Amir Abbas Mousavian 057c9fd940 Update podspec to 0.5.2 2016-09-30 14:08:25 +03:30
Amir Abbas Mousavian 6d63322779 FileProviderOperationDelegate methods will call for WebDav and Dropbox providers
- Added Carthage to Travis
2016-09-30 14:07:02 +03:30
Amir Abbas Mousavian fc6b46d17a Updated swift 3 semantics 2016-09-28 19:15:05 +03:30
Amir Abbas Mousavian bb9c08e309 Add .swift-version to pass cocoapods's validation, added static type var 2016-09-27 16:59:54 +03:30
Amir Abbas Mousavian 315ead606e AnyObject casts changed to proper one according to data type 2016-09-17 12:12:52 +04:30
Amir Abbas Mousavian d63e9c7f04 Swift 3 travis & podspec update 2016-09-16 05:20:34 +04:30
Amir Abbas Mousavian a5fe28c18d Swift 3 readme update 2016-09-15 07:58:36 +04:30
Amir Abbas Mousavian 542c18bab6 Convert to Swift 2 2016-09-15 02:48:04 +04:30
Amir Abbas Mousavian dca5dddbfd Code beat exceptions added 2016-08-20 19:19:05 +04:30
Amir Abbas Mousavian 6764c23f1b Update README.md and some refactors 2016-08-15 13:50:03 +04:30
Amir Abbas Mousavian 955a86372b Refactored NSURLSession delegate in Dropbox and WebDAV 2016-08-15 13:30:37 +04:30
Amir Abbas Mousavian 32a20fbc49 CodeCov added 2016-08-11 14:26:24 +04:30
Amir Abbas Mousavian 921ba19afa travis yml update 2016-08-11 14:16:04 +04:30
Amir Abbas Mousavian 8625b0464c Travis CI integration 2016-08-11 14:04:37 +04:30
Amir Abbas Mousavian 1bf9d1eadd Swift Package Manager Added 2016-08-11 13:49:28 +04:30
Amir Abbas Mousavian cbf774ba9e update README.md 2016-08-11 12:52:04 +04:30
Amir Abbas Mousavian 5e2f911cda Dropbox provider bug fixes 2016-08-11 01:06:53 +04:30
Amir Abbas Mousavian f423834021 HTTP error codes enum optimization 2016-08-10 12:35:11 +04:30
Amir Abbas Mousavian 0772c87122 FPSStreamTass bugs fixed and now working like a charm! 2016-08-09 23:37:28 +04:30
Amir Abbas Mousavian 011c535760 FPSStreamTask (iOS7,8) on a par with NSURLSessionStreamTask in iOS9 2016-08-09 13:45:35 +04:30
Amir Abbas Mousavian 468a2bc9e9 SMB2.QueryInfo implementation 2016-08-09 02:04:42 +04:30
Amir Abbas Mousavian 33be492499 Implemented SMB.ChangeNotify command, syntax fixed 2016-08-08 15:57:08 +04:30
Amir Abbas Mousavian c4049b961f Dropbox updates, SMB2 QueryDirectory implementation
- ExtendedFileProvider protocol is implemented for Dropbox
-
2016-08-08 12:47:13 +04:30
Amir Abbas Mousavian 940c7c1028 Dropbox implementation completed and getting storage size, used 2016-08-04 23:06:24 +04:30
Amir Abbas Mousavian b4ace7e680 Unified HTTP based services (WebDAV/Dropbox) Error Handling
- DropboxFileProvider.contentsOfDirectory() method implemented
2016-08-03 13:40:12 +04:30
Amir Abbas Mousavian eccbeb7174 Fixed WebDAV connection error for Apache servers 2016-08-02 14:59:32 +04:30
Amir Abbas Mousavian a077d000bc Updated Readme 2016-07-31 01:27:13 +04:30
Amir Abbas Mousavian 6a3ea633bf WebDAV file protocol conformance to FileProvider 2016-07-29 10:57:18 +04:30
Amir Abbas Mousavian 3ca26ad3df pod 0.3.2 2016-07-28 15:10:17 +04:30
Amir Abbas Mousavian 364b93c6fd Modifier correction 2016-07-28 14:31:48 +04:30
Amir Abbas Mousavian 506952be13 Corrected modifiers and updated pod to 0.3.1 2016-07-28 14:26:37 +04:30
Amir Abbas Mousavian 69ca35f0ae Source compatibility with Swift 2.3 2016-07-27 16:12:11 +04:30
Amir Abbas Mousavian dcbe3c1c3e Updated sourcefiles tag 2016-07-27 15:19:41 +04:30
Amir Abbas Mousavian e6a450e3e2 tvOS support 2016-07-27 15:02:14 +04:30
Amir Abbas Mousavian 0fd6da3658 Cocoapod project added 2016-07-27 14:56:33 +04:30
Amir Abbas Mousavian 67b6cf64e2 insert slash in the beginning of path 2016-07-26 10:34:34 +04:30
Amir Abbas Mousavian 3ab252e598 WebDav refactors and Dropbox partial implementation 2016-07-25 19:18:13 +04:30
Amir Abbas Mousavian 09f9909363 spliced SMB2 Types into several files 2016-07-21 00:40:21 +04:30
Amir Abbas Mousavian c6d069cb46 Added Write and IOCtl commands' types 2016-07-20 17:59:25 +04:30
51 changed files with 8396 additions and 4069 deletions
+1
View File
@@ -0,0 +1 @@
3.0
+27
View File
@@ -0,0 +1,27 @@
language: objective-c
osx_image: xcode8
xcode_project: FileProvider.xcodeproj
env:
global:
- FRAMEWORK_NAME=FileProvider.framework
before_install:
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- brew update
- brew outdated carthage || brew upgrade carthage
script:
- set pipefail
- xcodebuild -version
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider OSX" -sdk macosx | xcpretty
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider iOS" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider tvOS" -sdk appletvsimulator ONLY_ACTIVE_ARCH=NO | xcpretty
- pod lib lint --quick
after_success:
- bash <(curl -s https://codecov.io/bash)
before_deploy:
- carthage build --no-skip-current
- carthage archive FileProvider
deploy:
file: FileProvider.framework.zip
skip_cleanup: true
on:
tags: true
+5 -5
View File
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.2.0"
s.summary = "NSFileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS."
s.version = "0.5.2"
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS."
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
@@ -73,7 +73,7 @@ Pod::Spec.new do |s|
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.10"
# s.watchos.deployment_target = "2.0"
# s.tvos.deployment_target = "9.0"
s.tvos.deployment_target = "9.0"
# ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
@@ -93,8 +93,8 @@ Pod::Spec.new do |s|
# Not including the public_header_files will make all headers public.
#
s.source_files = "Source/*.swift"
s.exclude_files = "Source/Exclude"
s.source_files = "Sources/**/*.swift"
s.exclude_files = "Sources/Exclude"
# s.public_header_files = "Classes/**/*.h"
+934
View File
@@ -0,0 +1,934 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
7902C0861D61B56D00564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
7902C0871D61B67100564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
7902C0881D61B67100564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
7924B1931D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
7924B1941D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
7924B1951D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
7924B1961D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
7924B1971D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
7924B1981D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
7924B1991D89DAE000589DB7 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18E1D89DAE000589DB7 /* Element.swift */; };
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18E1D89DAE000589DB7 /* Element.swift */; };
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18E1D89DAE000589DB7 /* Element.swift */; };
7924B19C1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
7924B19D1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
7924B19E1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
7924B19F1D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
7924B1A01D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
7924B1A11D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
7924B1A51D89DAE000589DB7 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1921D89DAE000589DB7 /* Parser.swift */; };
7924B1A61D89DAE000589DB7 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1921D89DAE000589DB7 /* Parser.swift */; };
7924B1A71D89DAE000589DB7 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1921D89DAE000589DB7 /* Parser.swift */; };
7924B1AD1D89F7D800589DB7 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
7924B1AE1D89F7D900589DB7 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */; };
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */; };
7924B1B21D89FCDA00589DB7 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */; };
7924B1B31D89FD6400589DB7 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
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 */; };
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396B01D48C02300086753 /* FileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396951D48C02300086753 /* FileProvider.swift */; };
799396B11D48C02300086753 /* FileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396951D48C02300086753 /* FileProvider.swift */; };
799396B21D48C02300086753 /* FileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396951D48C02300086753 /* FileProvider.swift */; };
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BC1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BD1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BE1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
799396C21D48C02300086753 /* SMB2FileHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969C1D48C02300086753 /* SMB2FileHandle.swift */; };
799396C31D48C02300086753 /* SMB2FileHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969C1D48C02300086753 /* SMB2FileHandle.swift */; };
799396C41D48C02300086753 /* SMB2FileHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969C1D48C02300086753 /* SMB2FileHandle.swift */; };
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969D1D48C02300086753 /* SMB2FileOperation.swift */; };
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969D1D48C02300086753 /* SMB2FileOperation.swift */; };
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969D1D48C02300086753 /* SMB2FileOperation.swift */; };
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969E1D48C02300086753 /* SMB2IOCtl.swift */; };
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969E1D48C02300086753 /* SMB2IOCtl.swift */; };
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969E1D48C02300086753 /* SMB2IOCtl.swift */; };
799396CB1D48C02300086753 /* SMB2Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969F1D48C02300086753 /* SMB2Query.swift */; };
799396CC1D48C02300086753 /* SMB2Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969F1D48C02300086753 /* SMB2Query.swift */; };
799396CD1D48C02300086753 /* SMB2Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969F1D48C02300086753 /* SMB2Query.swift */; };
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A01D48C02300086753 /* SMB2Session.swift */; };
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A01D48C02300086753 /* SMB2Session.swift */; };
799396D01D48C02300086753 /* SMB2Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A01D48C02300086753 /* SMB2Session.swift */; };
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A11D48C02300086753 /* SMB2SetInfo.swift */; };
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A11D48C02300086753 /* SMB2SetInfo.swift */; };
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A11D48C02300086753 /* SMB2SetInfo.swift */; };
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A21D48C02300086753 /* SMB2Tree.swift */; };
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A21D48C02300086753 /* SMB2Tree.swift */; };
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A21D48C02300086753 /* SMB2Tree.swift */; };
799396D71D48C02300086753 /* SMB2Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A31D48C02300086753 /* SMB2Types.swift */; };
799396D81D48C02300086753 /* SMB2Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A31D48C02300086753 /* SMB2Types.swift */; };
799396D91D48C02300086753 /* SMB2Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A31D48C02300086753 /* SMB2Types.swift */; };
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 */; };
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 */
7902C0851D61B56D00564440 /* SessionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionDelegate.swift; sourceTree = "<group>"; };
7924B18C1D89DAE000589DB7 /* AEXML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEXML.h; sourceTree = "<group>"; };
7924B18D1D89DAE000589DB7 /* Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; };
7924B18E1D89DAE000589DB7 /* Element.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
7924B18F1D89DAE000589DB7 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
7924B1901D89DAE000589DB7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7924B1911D89DAE000589DB7 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = "<group>"; };
7924B1921D89DAE000589DB7 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPSStreamTask.swift; sourceTree = "<group>"; };
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>"; };
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; };
7993968B1D48B8C700086753 /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
7993968C1D48B8C700086753 /* Info-MacOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-MacOS.plist"; sourceTree = "<group>"; };
7993968D1D48B8C700086753 /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
799396931D48C02300086753 /* DropboxFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropboxFileProvider.swift; sourceTree = "<group>"; };
799396941D48C02300086753 /* FileProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileProvider.h; sourceTree = "<group>"; };
799396951D48C02300086753 /* FileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileProvider.swift; sourceTree = "<group>"; };
799396961D48C02300086753 /* LocalFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalFileProvider.swift; sourceTree = "<group>"; };
799396971D48C02300086753 /* SMBClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBClient.swift; sourceTree = "<group>"; };
799396981D48C02300086753 /* SMBFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBFileProvider.swift; sourceTree = "<group>"; };
7993969A1D48C02300086753 /* CIFSTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIFSTypes.swift; sourceTree = "<group>"; };
7993969B1D48C02300086753 /* SMB2DataTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2DataTypes.swift; sourceTree = "<group>"; };
7993969C1D48C02300086753 /* SMB2FileHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2FileHandle.swift; sourceTree = "<group>"; };
7993969D1D48C02300086753 /* SMB2FileOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2FileOperation.swift; sourceTree = "<group>"; };
7993969E1D48C02300086753 /* SMB2IOCtl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2IOCtl.swift; sourceTree = "<group>"; };
7993969F1D48C02300086753 /* SMB2Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Query.swift; sourceTree = "<group>"; };
799396A01D48C02300086753 /* SMB2Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Session.swift; sourceTree = "<group>"; };
799396A11D48C02300086753 /* SMB2SetInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2SetInfo.swift; sourceTree = "<group>"; };
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>"; };
799396A61D48C02300086753 /* WebDAVFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebDAVFileProvider.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
799396631D48B7F600086753 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
799396711D48B80D00086753 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
7993967E1D48B82700086753 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7924B18B1D89DAE000589DB7 /* AEXML */ = {
isa = PBXGroup;
children = (
7924B18C1D89DAE000589DB7 /* AEXML.h */,
7924B18D1D89DAE000589DB7 /* Document.swift */,
7924B18E1D89DAE000589DB7 /* Element.swift */,
7924B18F1D89DAE000589DB7 /* Error.swift */,
7924B1901D89DAE000589DB7 /* Info.plist */,
7924B1911D89DAE000589DB7 /* Options.swift */,
7924B1921D89DAE000589DB7 /* Parser.swift */,
);
path = AEXML;
sourceTree = "<group>";
};
7993965B1D48B7BF00086753 = {
isa = PBXGroup;
children = (
799396911D48C02300086753 /* Sources */,
7993968A1D48B8C700086753 /* Pod */,
799396681D48B7F600086753 /* Products */,
);
sourceTree = "<group>";
};
799396681D48B7F600086753 /* Products */ = {
isa = PBXGroup;
children = (
799396671D48B7F600086753 /* FileProvider.framework */,
799396751D48B80D00086753 /* FileProvider.framework */,
799396821D48B82700086753 /* FileProvider.framework */,
);
name = Products;
sourceTree = "<group>";
};
7993968A1D48B8C700086753 /* Pod */ = {
isa = PBXGroup;
children = (
7993968B1D48B8C700086753 /* Info-iOS.plist */,
7993968C1D48B8C700086753 /* Info-MacOS.plist */,
7993968D1D48B8C700086753 /* Info-tvOS.plist */,
);
path = Pod;
sourceTree = "<group>";
};
799396911D48C02300086753 /* Sources */ = {
isa = PBXGroup;
children = (
799396991D48C02300086753 /* SMBTypes */,
799396931D48C02300086753 /* DropboxFileProvider.swift */,
7924B18B1D89DAE000589DB7 /* AEXML */,
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
799396941D48C02300086753 /* FileProvider.h */,
799396951D48C02300086753 /* FileProvider.swift */,
799396961D48C02300086753 /* LocalFileProvider.swift */,
7902C0851D61B56D00564440 /* SessionDelegate.swift */,
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
799396971D48C02300086753 /* SMBClient.swift */,
799396981D48C02300086753 /* SMBFileProvider.swift */,
799396A61D48C02300086753 /* WebDAVFileProvider.swift */,
);
path = Sources;
sourceTree = "<group>";
};
799396991D48C02300086753 /* SMBTypes */ = {
isa = PBXGroup;
children = (
7993969A1D48C02300086753 /* CIFSTypes.swift */,
799396A41D48C02300086753 /* SMBErrorType.swift */,
7993969B1D48C02300086753 /* SMB2DataTypes.swift */,
799396A31D48C02300086753 /* SMB2Types.swift */,
799396A21D48C02300086753 /* SMB2Tree.swift */,
799396A01D48C02300086753 /* SMB2Session.swift */,
7993969D1D48C02300086753 /* SMB2FileOperation.swift */,
7993969C1D48C02300086753 /* SMB2FileHandle.swift */,
7993969E1D48C02300086753 /* SMB2IOCtl.swift */,
7993969F1D48C02300086753 /* SMB2Query.swift */,
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */,
794C22091D5893F800EC49B8 /* SMB2Notification.swift */,
799396A11D48C02300086753 /* SMB2SetInfo.swift */,
);
path = SMBTypes;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
799396641D48B7F600086753 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1931D89DAE000589DB7 /* AEXML.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
799396721D48B80D00086753 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1941D89DAE000589DB7 /* AEXML.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
7993967F1D48B82700086753 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1951D89DAE000589DB7 /* AEXML.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
799396661D48B7F600086753 /* FileProvider iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7993966F1D48B7F600086753 /* Build configuration list for PBXNativeTarget "FileProvider iOS" */;
buildPhases = (
799396621D48B7F600086753 /* Sources */,
799396631D48B7F600086753 /* Frameworks */,
799396641D48B7F600086753 /* Headers */,
799396651D48B7F600086753 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "FileProvider iOS";
productName = "FileProvider iOS";
productReference = 799396671D48B7F600086753 /* FileProvider.framework */;
productType = "com.apple.product-type.framework";
};
799396741D48B80D00086753 /* FileProvider OSX */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7993967A1D48B80D00086753 /* Build configuration list for PBXNativeTarget "FileProvider OSX" */;
buildPhases = (
799396701D48B80D00086753 /* Sources */,
799396711D48B80D00086753 /* Frameworks */,
799396721D48B80D00086753 /* Headers */,
799396731D48B80D00086753 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "FileProvider OSX";
productName = "FileProvider OSX";
productReference = 799396751D48B80D00086753 /* FileProvider.framework */;
productType = "com.apple.product-type.framework";
};
799396811D48B82700086753 /* FileProvider tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 799396871D48B82700086753 /* Build configuration list for PBXNativeTarget "FileProvider tvOS" */;
buildPhases = (
7993967D1D48B82700086753 /* Sources */,
7993967E1D48B82700086753 /* Frameworks */,
7993967F1D48B82700086753 /* Headers */,
799396801D48B82700086753 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "FileProvider tvOS";
productName = "FileProvider tvOS";
productReference = 799396821D48B82700086753 /* FileProvider.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7993965C1D48B7BF00086753 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0800;
TargetAttributes = {
799396661D48B7F600086753 = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 0800;
};
799396741D48B80D00086753 = {
CreatedOnToolsVersion = 7.3.1;
};
799396811D48B82700086753 = {
CreatedOnToolsVersion = 7.3.1;
};
};
};
buildConfigurationList = 7993965F1D48B7BF00086753 /* Build configuration list for PBXProject "FileProvider" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 7993965B1D48B7BF00086753;
productRefGroup = 799396681D48B7F600086753 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
799396661D48B7F600086753 /* FileProvider iOS */,
799396741D48B80D00086753 /* FileProvider OSX */,
799396811D48B82700086753 /* FileProvider tvOS */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
799396651D48B7F600086753 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7924B19F1D89DAE000589DB7 /* Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
799396731D48B80D00086753 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1A01D89DAE000589DB7 /* Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
799396801D48B82700086753 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1A11D89DAE000589DB7 /* Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
799396621D48B7F600086753 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */,
7924B1991D89DAE000589DB7 /* Element.swift in Sources */,
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
7924B1B21D89FCDA00589DB7 /* FPSStreamTask.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 */,
7924B1961D89DAE000589DB7 /* Document.swift in Sources */,
7902C0861D61B56D00564440 /* SessionDelegate.swift in Sources */,
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */,
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C21D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CB1D48C02300086753 /* SMB2Query.swift in Sources */,
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
7924B1B31D89FD6400589DB7 /* SMBClient.swift in Sources */,
7924B1A51D89DAE000589DB7 /* Parser.swift in Sources */,
799396B01D48C02300086753 /* FileProvider.swift in Sources */,
7924B19C1D89DAE000589DB7 /* Error.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
799396701D48B80D00086753 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */,
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */,
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */,
799396C91D48C02300086753 /* SMB2IOCtl.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 */,
7924B1971D89DAE000589DB7 /* Document.swift in Sources */,
7902C0871D61B67100564440 /* SessionDelegate.swift in Sources */,
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */,
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C31D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CC1D48C02300086753 /* SMB2Query.swift in Sources */,
7924B1AD1D89F7D800589DB7 /* SMBClient.swift in Sources */,
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
7924B1A61D89DAE000589DB7 /* Parser.swift in Sources */,
799396B11D48C02300086753 /* FileProvider.swift in Sources */,
7924B19D1D89DAE000589DB7 /* Error.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
7993967D1D48B82700086753 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */,
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */,
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */,
799396CA1D48C02300086753 /* SMB2IOCtl.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 */,
7924B1981D89DAE000589DB7 /* Document.swift in Sources */,
7902C0881D61B67100564440 /* SessionDelegate.swift in Sources */,
799396D01D48C02300086753 /* SMB2Session.swift in Sources */,
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C41D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CD1D48C02300086753 /* SMB2Query.swift in Sources */,
7924B1AE1D89F7D900589DB7 /* SMBClient.swift in Sources */,
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
7924B1A71D89DAE000589DB7 /* Parser.swift in Sources */,
799396B21D48C02300086753 /* FileProvider.swift in Sources */,
7924B19E1D89DAE000589DB7 /* Error.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
ONLY_ACTIVE_ARCH = YES;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
};
name = Release;
};
7993966D1D48B7F600086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-iOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-iOS";
PRODUCT_NAME = FileProvider;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
7993966E1D48B7F600086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-iOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-iOS";
PRODUCT_NAME = FileProvider;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
7993967B1D48B80D00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
FRAMEWORK_VERSION = A;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-MacOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-OSX";
PRODUCT_NAME = FileProvider;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
7993967C1D48B80D00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_VERSION = A;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-MacOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-OSX";
PRODUCT_NAME = FileProvider;
SDKROOT = macosx;
SKIP_INSTALL = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
799396881D48B82700086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-tvOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-tvOS";
PRODUCT_NAME = FileProvider;
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
799396891D48B82700086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-tvOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-tvOS";
PRODUCT_NAME = FileProvider;
SDKROOT = appletvos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7993965F1D48B7BF00086753 /* Build configuration list for PBXProject "FileProvider" */ = {
isa = XCConfigurationList;
buildConfigurations = (
799396601D48B7BF00086753 /* Debug */,
799396611D48B7BF00086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7993966F1D48B7F600086753 /* Build configuration list for PBXNativeTarget "FileProvider iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7993966D1D48B7F600086753 /* Debug */,
7993966E1D48B7F600086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7993967A1D48B80D00086753 /* Build configuration list for PBXNativeTarget "FileProvider OSX" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7993967B1D48B80D00086753 /* Debug */,
7993967C1D48B80D00086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
799396871D48B82700086753 /* Build configuration list for PBXNativeTarget "FileProvider tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
799396881D48B82700086753 /* Debug */,
799396891D48B82700086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7993965C1D48B7BF00086753 /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:FileProvider.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396741D48B80D00086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider OSX"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396741D48B80D00086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider OSX"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396741D48B80D00086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider OSX"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396661D48B7F600086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider iOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396661D48B7F600086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider iOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396661D48B7F600086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider iOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396811D48B82700086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider tvOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396811D48B82700086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider tvOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396811D48B82700086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider tvOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+5
View File
@@ -0,0 +1,5 @@
import PackageDescription
let package = Package(
name: "FileProvider"
)
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
+84 -62
View File
@@ -4,16 +4,19 @@
[![Swift Version][swift-image]][swift-url]
[![License][license-image]][license-url]
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20OSX-lightgray.svg)]()
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://img.shields.io/cocoapods/v/FileProvider.svg)
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20macOS%2C%20tvOS-lightgray.svg)]()
[![Build Status][travis-image]][travis-url]
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://cocoapods.org/pods/FileProvider)
[![codebeat badge][codebeat-image]][codebeat-url]
<!---
[![Build Status][travis-image]][travis-url]
[![codecov](https://codecov.io/gh/amosavian/FileProvider/branch/master/graph/badge.svg)](https://codecov.io/gh/amosavian/FileProvider)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
--->
This library provides implementaion of WebDav and SMB/CIFS (incomplete) and local files.
This library provides implementaion of WebDav and SMB2 (incomplete) and local files.
All functions are async calls and it wont block your main thread.
@@ -21,18 +24,20 @@ 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**
- [ ] **FTPFileProvider**
- [ ] **AmazonS3FileProvider**
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like searching and reading a portion of file.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission 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 deprecated in 1990s, it's still in use on some Web hosts.
- [ ] **AmazonS3FileProvider**
## Requirements
- **Swift 2.2**
- **Swift 3**
- iOS 8.0 , OSX 10.10
- XCode 7.3
- XCode 8.0
Legacy version is available in swift-2 branch
## Installation
@@ -73,19 +78,23 @@ For LocalFileProvider if you want to deal with `Documents` folder
is equal to:
let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
let documentsURL = NSURL(fileURLWithPath: documentPath);
let documentsURL = URL(fileURLWithPath: documentPath);
let documentsProvider = LocalFileProvider(baseURL: documentsURL)
You can't change the base url later. and all paths are related to this base url by default.
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 credential = URLCredential(user: "user", password: "pass", persistence: URLCredentialPersistence.Permanent)
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.
For interaction with UI, set delegate variable of `FileProvider` object
You can use `absoluteURL()` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support)
You can use `absoluteURL()` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support and returns path simply)
### Delegates
@@ -96,35 +105,35 @@ It's simply tree method which indicated whether the operation failed, succeed an
Your class should conforms `FileProviderDelegate` class:
override func viewDidLoad() {
documentsProvider.delegate = self
documentsProvider.delegate = self as FileProviderDelegate
}
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).")
case .Remove(path: let path):
NSLog("\(path) has been deleted.")
case .copy(source: let source, destination: let dest):
print("\(source) copied to \(dest).")
case .remove(path: let path):
print("\(path) has been deleted.")
default:
break
}
}
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.")
case .Remove(path: let path):
NSLog("\(path) can't be deleted.")
case .copy(source: let source, destination: let dest):
print("copy of \(source) failed.")
case .remove(path: let path):
print("\(path) can't be deleted.")
default:
break
}
}
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.")
case .copy(source: let source, destination: let dest):
print("Copy\(source) to \(dest): \(progress * 100) completed.")
default:
break
}
@@ -150,27 +159,37 @@ 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
documentsProvider.attributesOfItem(path: "/file.txt", completionHandler: {
(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
documentsProvider.contentsOfDirectory(path: "/", completionHandler: {
(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
@@ -183,66 +202,66 @@ You can then pass "" (empty string) to contentsOfDirectoryAtPath method to list
Creating new directory:
documentsProvider.createFolder(folderName: "new folder", atPath: "/", completionHandler: nil)
documentsProvider.create(folder: "new folder", at: "/", completionHandler: nil)
Creating new file from data stream:
let data = "hello world!".dataUsingEncoding(NSUTF8StringEncoding)
let file = FileObject(name: "old.txt", createdDate: NSDate(), modifiedDate: NSDate(), isHidden: false, isReadOnly: true)
documentsProvider.createFile(fileAttribs: file, atPath: "/", contents: data, completionHandler: nil)
let data = "hello world!".data(encoding: String.encoding.utf8)
let file = FileObject(name: "old.txt", createdDate: Date(), modifiedDate: Date(), isHidden: false, isReadOnly: true)
documentsProvider.create(file: file, at: "/", contents: data, completionHandler: nil)
### Copy and Move/Rename Files
Copy file old.txt to new.txt in current path:
documentsProvider.copyItemAtPath(path: "new folder/old.txt", toPath: "new.txt", overwrite: false, completionHandler: nil)
documentsProvider.copyItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
Move file old.txt to new.txt in current path:
documentsProvider.moveItemAtPath(path: "new folder/old.txt", toPath: "new.txt", overwrite: false, completionHandler: nil)
documentsProvider.moveItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
### Delete Files
documentsProvider.removeItemAtPath(path: "new.txt", completionHandler: nil)
documentsProvider.removeItem(path: "new.txt", completionHandler: nil)
***Caution:*** This method will delete directories with all it's content recursively.
### 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
documentsProvider.contents(path: "old.txt", completionHandler: {
(contents: Data?, error: ErrorType?) -> Void
if let contents = contents {
print(String(data: contents, encoding: NSUTF8StringEncoding)) // "hello world!"
print(String(data: contents, encoding: String.encoding.utf8)) // "hello world!"
}
})
If you want to retrieve a portion of file you should can `contentsAtPath` method with offset and length arguments. Please note first byte of file has offset: 0.
documentsProvider.contentsAtPath(path: "old.txt", offset: 2, length: 5, completionHandler: {
(contents: NSData?, error: ErrorType?) -> Void
documentsProvider.contents(path: "old.txt", offset: 2, length: 5, completionHandler: {
(contents: Data?, error: ErrorType?) -> Void
if let contents = contents {
print(String(data: contents, encoding: NSUTF8StringEncoding)) // "llo w"
print(String(data: contents, encoding: String.encoding.utf8)) // "llo w"
}
})
### Write Data To Files
let data = "What's up Newyork!".dataUsingEncoding(NSUTF8StringEncoding)
documentsProvider.writeContentsAtPath(path: "old.txt", contents data: data, atomically: true, completionHandler: nil)
let data = "What's up Newyork!".data(encoding: String.encoding.utf8)
documentsProvider.writeContents(path: "old.txt", content: data, atomically: true, completionHandler: nil)
### Monitoring FIle Changes
You can monitor updates in some file system (Local and SMB2), there is three methods in supporting provider you can use to register a handler, to unregister and to check whether it's being monitored or not. It's useful to find out when new files added or removed from directory and update user interface. The handler will be dispatched to main threads to avoid UI bugs with a 0.25 sec delay.
documentsProvider.registerNotifcation(provider.currentPath)
documentsProvider.registerNotifcation(path: provider.currentPath)
{
// calling functions to update UI
}
// To discontinue monitoring folders:
documentsProvider.unregisterNotifcation(provider.currentPath)
documentsProvider.unregisterNotifcation(path: provider.currentPath)
* **Please note** in LocalFileProvider it will also monitor changes in subfolders. This behaviour can varies according to file system specification.
@@ -250,23 +269,26 @@ 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)
Distributed under the MIT license. See `LICENSE` for more information.
[https://github.com/yourname/github-link](https://github.com/dbader/)
[https://github.com/amosavian/](https://github.com/amosavian/)
[swift-image]:https://img.shields.io/badge/swift-2.2-green.svg
[swift-image]:https://img.shields.io/badge/swift-3.0-orange.svg
[swift-url]: https://swift.org/
[license-image]: https://img.shields.io/badge/License-MIT-blue.svg
[license-url]: LICENSE
[codebeat-image]: https://codebeat.co/badges/7b359f48-78eb-4647-ab22-56262a827517
[codebeat-url]: https://codebeat.co/projects/github-com-amosavian-fileprovider
<!---
[travis-image]: https://img.shields.io/travis/dbader/node-datadog-metrics/master.svg?style=flat-square
[travis-url]: https://travis-ci.org/dbader/node-datadog-metrics
--->
[travis-image]: https://img.shields.io/travis/amosavian/FileProvider/master.svg
[travis-url]: https://travis-ci.org/amosavian/FileProvider
-492
View File
@@ -1,492 +0,0 @@
//
// AEXML.swift
//
// Copyright (c) 2014 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - AEXMLElement
/**
This is base class for holding XML structure.
You can access its structure by using subscript like this: `element["foo"]["bar"]` which would
return `<bar></bar>` element from `<element><foo><bar></bar></foo></element>` XML as an `AEXMLElement` object.
*/
public class AEXMLElement {
/// A type representing an error value that can be inside `error` property.
public enum Error: ErrorType {
case ElementNotFound
case RootElementMissing
}
private struct Defaults {
static let name = String()
static let attributes = [String : String]()
}
// MARK: Properties
/// Every `AEXMLElement` should have its parent element instead of `AEXMLDocument` which parent is `nil`.
public private(set) weak var parent: AEXMLElement?
/// Child XML elements.
public private(set) var children: [AEXMLElement] = [AEXMLElement]()
/// XML Element name (defaults to empty string).
public var name: String
/// XML Element value.
public var value: String?
/// XML Element attributes (defaults to empty dictionary).
public var attributes: [String : String]
/// Error value (`nil` if there is no error).
public var error: Error?
/// String representation of `value` property (if `value` is `nil` this is empty String).
public var stringValue: String { return value ?? String() }
/// Boolean representation of `value` property (if `value` is "true" or 1 this is `True`, otherwise `False`).
public var boolValue: Bool { return stringValue.lowercaseString == "true" || Int(stringValue) == 1 ? true : false }
/// Integer representation of `value` property (this is **0** if `value` can't be represented as Integer).
public var intValue: Int { return Int(stringValue) ?? 0 }
/// Double representation of `value` property (this is **0.00** if `value` can't be represented as Double).
public var doubleValue: Double { return (stringValue as NSString).doubleValue }
// MARK: Lifecycle
/**
Designated initializer - all parameters are optional.
- parameter name: XML element name.
- parameter value: XML element value
- parameter attributes: XML element attributes
- returns: An initialized `AEXMLElement` object.
*/
public init(_ name: String? = nil, value: String? = nil, attributes: [String : String]? = nil) {
self.name = name ?? Defaults.name
self.value = value
self.attributes = attributes ?? Defaults.attributes
}
// MARK: XML Read
/// The first element with given name **(Empty element with error if not exists)**.
public subscript(key: String) -> AEXMLElement {
guard let
first = children.filter({ $0.name == key }).first
else {
let errorElement = AEXMLElement(key)
errorElement.error = Error.ElementNotFound
return errorElement
}
return first
}
/// Returns all of the elements with equal name as `self` **(nil if not exists)**.
public var all: [AEXMLElement]? { return parent?.children.filter { $0.name == self.name } }
/// Returns the first element with equal name as `self` **(nil if not exists)**.
public var first: AEXMLElement? { return all?.first }
/// Returns the last element with equal name as `self` **(nil if not exists)**.
public var last: AEXMLElement? { return all?.last }
/// Returns number of all elements with equal name as `self`.
public var count: Int { return all?.count ?? 0 }
private func allWithCondition(fulfillCondition: (element: AEXMLElement) -> Bool) -> [AEXMLElement]? {
var found = [AEXMLElement]()
if let elements = all {
for element in elements {
if fulfillCondition(element: element) {
found.append(element)
}
}
return found.count > 0 ? found : nil
} else {
return nil
}
}
/**
Returns all elements with given value.
- parameter value: XML element value.
- returns: Optional Array of found XML elements.
*/
public func allWithValue(value: String) -> [AEXMLElement]? {
let found = allWithCondition { (element) -> Bool in
return element.value == value
}
return found
}
/**
Returns all elements with given attributes.
- parameter attributes: Dictionary of Keys and Values of attributes.
- returns: Optional Array of found XML elements.
*/
public func allWithAttributes(attributes: [String : String]) -> [AEXMLElement]? {
let found = allWithCondition { (element) -> Bool in
var countAttributes = 0
for (key, value) in attributes {
if element.attributes[key] == value {
countAttributes += 1
}
}
return countAttributes == attributes.count
}
return found
}
// MARK: XML Write
/**
Adds child XML element to `self`.
- parameter child: Child XML element to add.
- returns: Child XML element with `self` as `parent`.
*/
public func addChild(child: AEXMLElement) -> AEXMLElement {
child.parent = self
children.append(child)
return child
}
/**
Adds child XML element to `self`.
- parameter name: Child XML element name.
- parameter value: Child XML element value.
- parameter attributes: Child XML element attributes.
- returns: Child XML element with `self` as `parent`.
*/
public func addChild(name name: String, value: String? = nil, attributes: [String : String]? = nil) -> AEXMLElement {
let child = AEXMLElement(name, value: value, attributes: attributes)
return addChild(child)
}
/// Removes `self` from `parent` XML element.
public func removeFromParent() {
parent?.removeChild(self)
}
private func removeChild(child: AEXMLElement) {
if let childIndex = children.indexOf({ $0 === child }) {
children.removeAtIndex(childIndex)
}
}
private var parentsCount: Int {
var count = 0
var element = self
while let parent = element.parent {
count += 1
element = parent
}
return count
}
private func indentation(depth: Int) -> String {
var count = depth
var indent = String()
while count > 0 {
indent += "\t"
count -= 1
}
return indent
}
/// Complete hierarchy of `self` and `children` in **XML** escaped and formatted String
public var xmlString: String {
var xml = String()
// open element
xml += indentation(parentsCount - 1)
xml += "<\(name)"
if attributes.count > 0 {
// insert attributes
for (key, value) in attributes {
xml += " \(key)=\"\(value.xmlEscaped)\""
}
}
if value == nil && children.count == 0 {
// close element
xml += " />"
} else {
if children.count > 0 {
// add children
xml += ">\n"
for child in children {
xml += "\(child.xmlString)\n"
}
// add indentation
xml += indentation(parentsCount - 1)
xml += "</\(name)>"
} else {
// insert string value and close element
xml += ">\(stringValue.xmlEscaped)</\(name)>"
}
}
return xml
}
/// Same as `xmlString` but without `\n` and `\t` characters
public var xmlStringCompact: String {
let chars = NSCharacterSet(charactersInString: "\n\t")
return xmlString.componentsSeparatedByCharactersInSet(chars).joinWithSeparator("")
}
}
public extension String {
/// String representation of self with XML special characters escaped.
public var xmlEscaped: String {
// we need to make sure "&" is escaped first. Not doing this may break escaping the other characters
var escaped = stringByReplacingOccurrencesOfString("&", withString: "&amp;", options: .LiteralSearch)
// replace the other four special characters
let escapeChars = ["<" : "&lt;", ">" : "&gt;", "'" : "&apos;", "\"" : "&quot;"]
for (char, echar) in escapeChars {
escaped = escaped.stringByReplacingOccurrencesOfString(char, withString: echar, options: .LiteralSearch)
}
return escaped
}
}
// MARK: - AEXMLDocument
/**
This class is inherited from `AEXMLElement` and has a few addons to represent **XML Document**.
XML Parsing is also done with this object.
*/
public class AEXMLDocument: AEXMLElement {
private struct Defaults {
static let version = 1.0
static let encoding = "utf-8"
static let standalone = "no"
static let documentName = "AEXMLDocument"
}
/// Default options used by NSXMLParser
public struct NSXMLParserOptions {
public var shouldProcessNamespaces = false
public var shouldReportNamespacePrefixes = false
public var shouldResolveExternalEntities = false
public init() {}
}
// MARK: Properties
/// This is only used for XML Document header (default value is 1.0).
public let version: Double
/// This is only used for XML Document header (default value is "utf-8").
public let encoding: String
/// This is only used for XML Document header (default value is "no").
public let standalone: String
/// Options for NSXMLParser (default values are `false`)
public let xmlParserOptions: NSXMLParserOptions
/// Root (the first child element) element of XML Document **(Empty element with error if not exists)**.
public var root: AEXMLElement {
guard let rootElement = children.first else {
let errorElement = AEXMLElement()
errorElement.error = Error.RootElementMissing
return errorElement
}
return rootElement
}
// MARK: Lifecycle
/**
Designated initializer - Creates and returns XML Document object.
- parameter version: Version value for XML Document header (defaults to 1.0).
- parameter encoding: Encoding value for XML Document header (defaults to "utf-8").
- parameter standalone: Standalone value for XML Document header (defaults to "no").
- parameter root: Root XML element for XML Document (defaults to `nil`).
- parameter xmlParserOptions: Options for NSXMLParser (defaults to `false` for all).
- returns: An initialized XML Document object.
*/
public init(version: Double = Defaults.version,
encoding: String = Defaults.encoding,
standalone: String = Defaults.standalone,
root: AEXMLElement? = nil,
xmlParserOptions: NSXMLParserOptions = NSXMLParserOptions())
{
// set document properties
self.version = version
self.encoding = encoding
self.standalone = standalone
self.xmlParserOptions = xmlParserOptions
// init super with default name
super.init(Defaults.documentName)
// document has no parent element
parent = nil
// add root element to document (if any)
if let rootElement = root {
addChild(rootElement)
}
}
/**
Convenience initializer - used for parsing XML data (by calling `loadXMLData:` internally).
- parameter version: Version value for XML Document header (defaults to 1.0).
- parameter encoding: Encoding value for XML Document header (defaults to "utf-8").
- parameter standalone: Standalone value for XML Document header (defaults to "no").
- parameter xmlData: XML data to parse.
- parameter xmlParserOptions: Options for NSXMLParser (defaults to `false` for all).
- returns: An initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(version: Double = Defaults.version,
encoding: String = Defaults.encoding,
standalone: String = Defaults.standalone,
xmlData: NSData,
xmlParserOptions: NSXMLParserOptions = NSXMLParserOptions()) throws
{
self.init(version: version, encoding: encoding, standalone: standalone, xmlParserOptions: xmlParserOptions)
try loadXMLData(xmlData)
}
// MARK: Read XML
/**
Creates instance of `AEXMLParser` (private class which is simple wrapper around `NSXMLParser`)
and starts parsing the given XML data. Throws error if data could not be parsed.
- parameter data: XML which should be parsed.
*/
public func loadXMLData(data: NSData) throws {
children.removeAll(keepCapacity: false)
let xmlParser = AEXMLParser(xmlDocument: self, xmlData: data)
try xmlParser.parse()
}
// MARK: Override
/// Override of `xmlString` property of `AEXMLElement` - it just inserts XML Document header at the beginning.
public override var xmlString: String {
var xml = "<?xml version=\"\(version)\" encoding=\"\(encoding)\" standalone=\"\(standalone)\"?>\n"
for child in children {
xml += child.xmlString
}
return xml
}
}
// MARK: - AEXMLParser
private class AEXMLParser: NSObject, NSXMLParserDelegate {
// MARK: Properties
let xmlDocument: AEXMLDocument
let xmlData: NSData
var currentParent: AEXMLElement?
var currentElement: AEXMLElement?
var currentValue = String()
var parseError: NSError?
// MARK: Lifecycle
init(xmlDocument: AEXMLDocument, xmlData: NSData) {
self.xmlDocument = xmlDocument
self.xmlData = xmlData
currentParent = xmlDocument
super.init()
}
// MARK: XML Parse
func parse() throws {
let parser = NSXMLParser(data: xmlData)
parser.delegate = self
parser.shouldProcessNamespaces = xmlDocument.xmlParserOptions.shouldProcessNamespaces
parser.shouldReportNamespacePrefixes = xmlDocument.xmlParserOptions.shouldReportNamespacePrefixes
parser.shouldResolveExternalEntities = xmlDocument.xmlParserOptions.shouldResolveExternalEntities
let success = parser.parse()
if !success {
throw parseError ?? NSError(domain: "net.tadija.AEXML", code: 1, userInfo: nil)
}
}
// MARK: NSXMLParserDelegate
@objc func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
currentValue = String()
currentElement = currentParent?.addChild(name: elementName, attributes: attributeDict)
currentParent = currentElement
}
@objc func parser(parser: NSXMLParser, foundCharacters string: String) {
currentValue += string
let newValue = currentValue.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
currentElement?.value = newValue == String() ? nil : newValue
}
@objc func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
currentParent = currentParent?.parent
currentElement = nil
}
@objc func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) {
self.parseError = parseError
}
}
-332
View File
@@ -1,332 +0,0 @@
//
// FileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
#if os(iOS)
import UIKit
public typealias ImageClass = UIImage
#elseif os(OSX)
import Cocoa
public typealias ImageClass = NSImage
#endif
public enum FileType: String {
case Directory
case Regular
case SymbolicLink
case Socket
case CharacterSpecial
case BlockSpecial
case NamedPipe
case Unknown
init(urlResourceTypeValue: String) {
switch urlResourceTypeValue {
case NSURLFileResourceTypeNamedPipe: self = .NamedPipe
case NSURLFileResourceTypeCharacterSpecial: self = .CharacterSpecial
case NSURLFileResourceTypeDirectory: self = .Directory
case NSURLFileResourceTypeBlockSpecial: self = .BlockSpecial
case NSURLFileResourceTypeRegular: self = .Regular
case NSURLFileResourceTypeSymbolicLink: self = .SymbolicLink
case NSURLFileResourceTypeSocket: self = .Socket
case NSURLFileResourceTypeUnknown: self = .Unknown
default: self = .Unknown
}
}
init(fileTypeValue: String) {
switch fileTypeValue {
case NSFileTypeCharacterSpecial: self = .CharacterSpecial
case NSFileTypeDirectory: self = .Directory
case NSFileTypeBlockSpecial: self = .BlockSpecial
case NSFileTypeRegular: self = .Regular
case NSFileTypeSymbolicLink: self = .SymbolicLink
case NSFileTypeSocket: self = .Socket
case NSFileTypeUnknown: self = .Unknown
default: self = .Unknown
}
}
}
protocol FoundationErrorEnum {
init? (rawValue: Int)
var rawValue: Int { get }
}
extension NSURLError: FoundationErrorEnum {}
extension NSCocoaError: FoundationErrorEnum {}
public class FileObject {
let absoluteURL: NSURL?
let name: String
let path: String
let size: Int64
let createdDate: NSDate?
let modifiedDate: NSDate?
let fileType: FileType
let isHidden: Bool
let isReadOnly: Bool
init(absoluteURL: NSURL?, name: String, path: String, size: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
self.absoluteURL = absoluteURL
self.name = name
self.path = path
self.size = size
self.createdDate = createdDate
self.modifiedDate = modifiedDate
self.fileType = fileType
self.isHidden = isHidden
self.isReadOnly = isReadOnly
}
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
}
var isDirectory: Bool {
return self.fileType == .Directory
}
var isSymLink: Bool {
return self.fileType == .SymbolicLink
}
}
public typealias SimpleCompletionHandler = ((error: ErrorType?) -> Void)?
public protocol FileProviderBasic: class {
var type: String { get }
var isPathRelative: Bool { get }
var baseURL: NSURL? { get }
var currentPath: String { get set }
var dispatch_queue: dispatch_queue_t { get set }
var delegate: FileProviderDelegate? { get set }
var credential: NSURLCredential? { get }
/**
*
*/
func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void))
func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void))
}
public protocol FileProviderOperations: FileProviderBasic {
var fileOperationDelegate : FileOperationDelegate? { get set }
func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler)
func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler)
func moveItemAtPath(path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
func copyItemAtPath(path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler)
func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler)
func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler)
}
public protocol FileProviderReadWrite: FileProviderBasic {
func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void))
func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void))
func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool, completionHandler: SimpleCompletionHandler)
func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void))
}
public protocol FileProviderMonitor: FileProviderBasic {
func registerNotifcation(path: String, eventHandler: (() -> Void))
func unregisterNotifcation(path: String)
func isRegisteredForNotification(path: String) -> Bool
}
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite {
}
extension FileProviderBasic {
var bareCurrentPath: String {
return currentPath.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: ". /"))
}
public func absoluteURL(path: String? = nil) -> NSURL {
let rpath: String
if let path = path {
rpath = path
} else {
rpath = self.currentPath
}
if isPathRelative, let baseURL = baseURL {
if rpath.hasPrefix("/") && baseURL.absoluteString.hasSuffix("/") {
var npath = rpath
npath.removeAtIndex(npath.startIndex)
return baseURL.URLByAppendingPathComponent(npath)
} else {
return baseURL.URLByAppendingPathComponent(rpath)
}
} else {
return NSURL(fileURLWithPath: rpath).URLByStandardizingPath!
}
}
public func relativePathOf(url url: NSURL) -> String {
guard let baseURL = self.baseURL else { return url.absoluteString }
return url.URLByStandardizingPath!.absoluteString.stringByReplacingOccurrencesOfString(baseURL.absoluteString, withString: "/").stringByRemovingPercentEncoding!
}
public func fileByUniqueName(filePath: String) -> String {
let fileUrl = NSURL(fileURLWithPath: filePath)
let dirPath = fileUrl.URLByDeletingLastPathComponent?.path ?? ""
guard let fileName = fileUrl.URLByDeletingPathExtension?.lastPathComponent else {
return filePath
}
let fileExt = fileUrl.pathExtension ?? ""
var result = fileName
let group = dispatch_group_create()
dispatch_group_enter(group)
self.contentsOfDirectoryAtPath(dirPath) { (contents, error) in
var bareFileName = fileName
let number = Int(fileName.componentsSeparatedByString(" ").filter {
!$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
}.last ?? "noname")
if let _ = number {
result = fileName.componentsSeparatedByString(" ").filter {
!$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
}.dropLast().joinWithSeparator(" ")
bareFileName = result
}
var i = number ?? 2
let similiar = contents.map {
$0.absoluteURL?.lastPathComponent ?? $0.name
}.filter {
$0.hasPrefix(result) && (!fileExt.isEmpty && $0.hasSuffix("." + fileExt))
}
while similiar.contains(result + (!fileExt.isEmpty ? "." + fileExt : "")) {
result = "\(bareFileName) \(i)"
i += 1
}
dispatch_group_leave(group)
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
let finalFile = result + (!fileExt.isEmpty ? "." + fileExt : "")
return (dirPath as NSString).stringByAppendingPathComponent(finalFile)
}
internal func throwError(path: String, code: FoundationErrorEnum) -> NSError {
let fileURL = self.absoluteURL(path)
let domain: String
switch code {
case is NSURLError:
domain = NSURLErrorDomain
default:
domain = NSCocoaErrorDomain
}
return NSError(domain: domain, code: code.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString])
}
internal func NotImplemented() {
assert(false, "method not implemented")
}
internal func resolveRFCDate(httpDateString: String) -> NSDate? {
let dateFor: NSDateFormatter = NSDateFormatter()
dateFor.locale = NSLocale(localeIdentifier: "en_US")
dateFor.dateFormat = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss zzz"
if let rfc1123 = dateFor.dateFromString(httpDateString) {
return rfc1123
}
dateFor.dateFormat = "EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z"
if let rfc850 = dateFor.dateFromString(httpDateString) {
return rfc850
}
dateFor.dateFormat = "EEE MMM d HH':'mm':'ss yyyy"
if let asctime = dateFor.dateFromString(httpDateString) {
return asctime
}
//self.init()
return nil
}
internal func jsonToDictionary(jsonString: String) -> [String: AnyObject]? {
guard let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) else {
return nil
}
if let dic = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [String: AnyObject] {
return dic
}
return nil
}
internal func dictionaryToJSON(dictionary: [String: AnyObject]) -> String? {
if let data = try? NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions()) {
return String(data: data, encoding: NSUTF8StringEncoding)
}
return nil
}
}
public protocol ExtendedFileProvider: FileProvider {
func thumbnailOfFileSupported(path: String) -> Bool
func propertiesOfFileSupported(path: String) -> Bool
func thumbnailOfFileAtPath(path: String, dimension: CGSize, completionHandler: ((image: ImageClass?, error: ErrorType?) -> Void))
func propertiesOfFileAtPath(path: String, completionHandler: ((propertiesDictionary: [String: AnyObject], keys: [String], error: ErrorType?) -> Void))
}
public enum FileOperation {
case Create (path: String)
case Copy (source: String, destination: String)
case Move (source: String, destination: String)
case Modify (path: String)
case Remove (path: String)
case Link (link: String, target: String)
var description: String {
switch self {
case .Create(path: _): return "Create"
case .Copy(source: _, destination: _): return "Copy"
case .Move(source: _, destination: _): return "Move"
case .Modify(path: _): return "Modify"
case .Remove(path: _): return "Remove"
case .Link(link: _, target: _): return "Link"
}
}
var actionDescription: String {
switch self {
case .Create(path: _): return "Creating"
case .Copy(source: _, destination: _): return "Copying"
case .Move(source: _, destination: _): return "Moving"
case .Modify(path: _): return "Modifying"
case .Remove(path: _): return "Removing"
case .Link(link: _, target: _): return "Linking"
}
}
}
public protocol FileProviderDelegate: class {
func fileproviderSucceed(fileProvider: FileProviderOperations, operation: FileOperation)
func fileproviderFailed(fileProvider: FileProviderOperations, operation: FileOperation)
func fileproviderProgress(fileProvider: FileProviderOperations, operation: FileOperation, progress: Float)
}
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.
func fileProvider(fileProvider: FileProviderOperations, shouldProceedAfterError error: ErrorType, operation: FileOperation) -> Bool
}
-492
View File
@@ -1,492 +0,0 @@
//
// LocalFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
public final class LocalFileObject: FileObject {
let allocatedSize: Int64
init(absoluteURL: NSURL, name: String, path: String, size: Int64, allocatedSize: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
self.allocatedSize = allocatedSize
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
}
public class LocalFileProvider: FileProvider, FileProviderMonitor {
public let type = "Local"
public var isPathRelative: Bool = true
public var baseURL: NSURL? = LocalFileProvider.defaultBaseURL()
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t
public var operation_queue: dispatch_queue_t
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential? = nil
public let fileManager = NSFileManager()
public let opFileManager = NSFileManager()
private var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
init () {
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
operation_queue = dispatch_queue_create("FileProvider.\(type).Operation", DISPATCH_QUEUE_SERIAL)
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
init (baseURL: NSURL) {
self.baseURL = baseURL
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
operation_queue = dispatch_queue_create("FileProvider.\(type).Operation", DISPATCH_QUEUE_SERIAL)
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
private static func defaultBaseURL() -> NSURL {
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
return NSURL(fileURLWithPath: paths[0])
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
do {
let contents = try self.fileManager.contentsOfDirectoryAtURL(self.absoluteURL(path), includingPropertiesForKeys: [NSURLNameKey, NSURLFileSizeKey, NSURLFileAllocatedSizeKey, NSURLCreationDateKey, NSURLContentModificationDateKey, NSURLIsHiddenKey, NSURLVolumeIsReadOnlyKey, NSFileGroupOwnerAccountName], options: NSDirectoryEnumerationOptions.SkipsSubdirectoryDescendants)
let filesAttributes = contents.map({ (fileURL) -> LocalFileObject in
return self.attributesOfItemAtURL(fileURL)
})
completionHandler(contents: filesAttributes, error: nil)
} catch let e as NSError {
completionHandler(contents: [], error: e)
}
}
}
internal func attributesOfItemAtURL(fileURL: NSURL) -> LocalFileObject {
var namev, sizev, allocated, filetypev, creationDatev, modifiedDatev, hiddenv, readonlyv: AnyObject?
_ = try? fileURL.getResourceValue(&namev, forKey: NSURLNameKey)
_ = try? fileURL.getResourceValue(&sizev, forKey: NSURLFileSizeKey)
_ = try? fileURL.getResourceValue(&allocated, forKey: NSURLFileAllocatedSizeKey)
_ = try? fileURL.getResourceValue(&creationDatev, forKey: NSURLCreationDateKey)
_ = try? fileURL.getResourceValue(&modifiedDatev, forKey: NSURLContentModificationDateKey)
_ = try? fileURL.getResourceValue(&filetypev, forKey: NSURLFileResourceTypeKey)
_ = try? fileURL.getResourceValue(&hiddenv, forKey: NSURLIsHiddenKey)
_ = try? fileURL.getResourceValue(&readonlyv, forKey: NSURLVolumeIsReadOnlyKey)
let path: String
if isPathRelative {
path = self.relativePathOf(url: fileURL)
} else {
path = fileURL.path!
}
let fileAttr = LocalFileObject(absoluteURL: fileURL, name: namev as! String, path: path, size: sizev?.longLongValue ?? -1, allocatedSize: allocated?.longLongValue ?? -1, createdDate: creationDatev as? NSDate, modifiedDate: modifiedDatev as? NSDate, fileType: FileType(urlResourceTypeValue: filetypev as? String ?? ""), isHidden: hiddenv?.boolValue ?? false, isReadOnly: readonlyv?.boolValue ?? false)
return fileAttr
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
completionHandler(attributes: self.attributesOfItemAtURL(self.absoluteURL(path)), error: nil)
}
}
public weak var fileOperationDelegate : FileOperationDelegate?
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
do {
try self.opFileManager.createDirectoryAtURL(self.absoluteURL(atPath).URLByAppendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"))
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"))
})
}
}
}
public func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
let fileURL = self.absoluteURL(atPath).URLByAppendingPathComponent(fileAttribs.name)
var attributes = [String : AnyObject]()
if let createdDate = fileAttribs.createdDate {
attributes[NSFileCreationDate] = createdDate
}
if let modDate = fileAttribs.modifiedDate {
attributes[NSFileModificationDate] = modDate
}
if fileAttribs.isReadOnly {
attributes[NSFilePosixPermissions] = NSNumber(short: 365 /*555 o*/)
}
let success = self.opFileManager.createFileAtPath(fileURL.path!, contents: data, attributes: attributes)
if success {
do {
try fileURL.setResourceValue(fileAttribs.isHidden, forKey: NSURLIsHiddenKey)
} catch _ {}
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(fileAttribs.name)))
})
} else {
completionHandler?(error: self.throwError(atPath, code: NSURLError.CannotCreateFile))
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(fileAttribs.name)))
})
}
}
}
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: progress
dispatch_async(operation_queue) {
if !overwrite && self.fileManager.fileExistsAtPath(self.absoluteURL(toPath).path ?? "") {
completionHandler?(error: self.throwError(toPath, code: NSURLError.CannotMoveFile))
return
}
do {
try self.opFileManager.moveItemAtURL(self.absoluteURL(path), toURL: self.absoluteURL(toPath))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Move(source: path, destination: toPath))
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Move(source: path, destination: toPath))
})
}
}
}
public func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: progress, for files > 100mb, monitor file by another thread, for dirs check copied items count
dispatch_async(operation_queue) {
if !overwrite && self.fileManager.fileExistsAtPath(self.absoluteURL(toPath).path ?? "") {
completionHandler?(error: self.throwError(toPath, code: NSURLError.CannotWriteToFile))
return
}
do {
try self.opFileManager.copyItemAtURL(self.absoluteURL(path), toURL: self.absoluteURL(toPath))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: path, destination: toPath))
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Copy(source: path, destination: toPath))
})
}
}
}
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
do {
try self.opFileManager.removeItemAtURL(self.absoluteURL(path))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Remove(path: path))
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Remove(path: path))
})
}
}
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
do {
try self.opFileManager.copyItemAtURL(localFile, toURL: self.absoluteURL(toPath))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: localFile.absoluteString, destination: toPath))
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Copy(source: localFile.absoluteString, destination: toPath))
})
}
}
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
do {
try self.opFileManager.copyItemAtURL(self.absoluteURL(path), toURL: toLocalURL)
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: path, destination: toLocalURL.absoluteString))
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Copy(source: path, destination: toLocalURL.absoluteString))
})
}
}
}
public func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
let data = self.fileManager.contentsAtPath(self.absoluteURL(path).path!)
completionHandler(contents: data, error: nil)
}
}
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
// Unfortunatlely there is no method provided in NSFileManager to read a segment of file.
// So we have to fallback to POSIX provided methods
dispatch_async(dispatch_queue) {
let aPath = self.absoluteURL(path).path!
if self.attributesOfItemAtURL(self.absoluteURL(path)).isDirectory {
self.throwError(path, code: NSURLError.FileIsDirectory)
}
if !self.fileManager.fileExistsAtPath(aPath) {
self.throwError(path, code: NSURLError.FileDoesNotExist)
}
let fd_from = open(aPath, O_RDONLY)
if fd_from < 0 {
completionHandler(contents: nil, error: self.throwError(path, code: NSURLError.CannotOpenFile))
}
defer { precondition(close(fd_from) >= 0) }
lseek(fd_from, offset, SEEK_SET)
var buf = [UInt8](count: length, repeatedValue: 0)
let nread = read(fd_from, &buf, buf.count)
if nread < 0 { self.throwError(path, code: NSURLError.NoPermissionsToReadFile) }
if nread == 0 {
completionHandler(contents: nil, error: nil)
} else {
let data = NSData(bytesNoCopy: &buf, length: nread, freeWhenDone: true)
completionHandler(contents: data, error: nil)
}
}
}
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
data.writeToURL(self.absoluteURL(path), atomically: atomically)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Modify(path: path))
})
}
}
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
let iterator = self.fileManager.enumeratorAtURL(self.absoluteURL(path), includingPropertiesForKeys: nil, options: recursive ? NSDirectoryEnumerationOptions() : .SkipsSubdirectoryDescendants) { (url, e) -> Bool in
completionHandler(files: [], error: e)
return true
}
var result = [LocalFileObject]()
while let fileURL = iterator?.nextObject() as? NSURL {
if fileURL.lastPathComponent?.lowercaseString.containsString(query.lowercaseString) ?? false {
let fileObject = self.attributesOfItemAtURL(fileURL)
result.append(self.attributesOfItemAtURL(fileURL))
foundItemHandler?(fileObject)
}
}
completionHandler(files: result, error: nil)
}
}
private var monitors = [LocalFolderMonitor]()
public func registerNotifcation(path: String, eventHandler: (() -> Void)) {
self.unregisterNotifcation(path)
let absurl = self.absoluteURL(path)
var isdirv: AnyObject?
do {
try absurl.getResourceValue(&isdirv, forKey: NSURLIsDirectoryKey)
} catch _ {
}
if !(isdirv?.boolValue ?? false) {
return
}
let monitor = LocalFolderMonitor(url: absurl) {
eventHandler()
}
monitor.start()
monitors.append(monitor)
}
public func unregisterNotifcation(path: String) {
var removedMonitor: LocalFolderMonitor?
for (i, monitor) in monitors.enumerate() {
if self.relativePathOf(url: monitor.url) == path {
removedMonitor = monitors.removeAtIndex(i)
}
}
removedMonitor?.stop()
}
public func isRegisteredForNotification(path: String) -> Bool {
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path)
}
}
extension LocalFileProvider {
public func createSymbolicLinkAtPath(path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
do {
try self.opFileManager.createSymbolicLinkAtURL(self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Link(link: path, target: destPath))
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Link(link: path, target: destPath))
})
}
}
}
}
class LocalFileProviderManagerDelegate: NSObject, NSFileManagerDelegate {
weak var provider: LocalFileProvider?
init(provider: LocalFileProvider) {
self.provider = provider
}
func fileManager(fileManager: NSFileManager, shouldCopyItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .Copy(source: srcPath, destination: dstPath))
}
func fileManager(fileManager: NSFileManager, shouldMoveItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .Move(source: srcPath, destination: dstPath))
}
func fileManager(fileManager: NSFileManager, shouldRemoveItemAtURL URL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return true
}
let path = provider.relativePathOf(url: URL)
return delegate.fileProvider(provider, shouldDoOperation: .Remove(path: path))
}
func fileManager(fileManager: NSFileManager, shouldLinkItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .Link(link: srcPath, target: dstPath))
}
func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, copyingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .Copy(source: srcPath, destination: dstPath))
}
func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, movingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .Move(source: srcPath, destination: dstPath))
}
func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, removingItemAtURL URL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return false
}
let path = provider.relativePathOf(url: URL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .Remove(path: path))
}
func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, linkingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .Link(link: srcPath, target: dstPath))
}
}
internal class LocalFolderMonitor {
private let source: dispatch_source_t
private let descriptor: CInt
private let qq: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
private var state: Bool = false
private var monitoredTime: NSTimeInterval = NSDate().timeIntervalSinceReferenceDate
var url: NSURL
/// Creates a folder monitor object with monitoring enabled.
init(url: NSURL, handler: ()->Void) {
self.url = url
descriptor = open(url.fileSystemRepresentation, O_EVTONLY)
source = dispatch_source_create(
DISPATCH_SOURCE_TYPE_VNODE,
UInt(descriptor),
DISPATCH_VNODE_WRITE,
qq
)
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
// affect user experince much
let main_handler: ()->Void = {
if NSDate().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
return
}
self.monitoredTime = NSDate().timeIntervalSinceReferenceDate
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC) / 4), dispatch_get_main_queue(), {
handler()
})
}
dispatch_source_set_event_handler(source, main_handler)
dispatch_source_set_cancel_handler(source) {
close(self.descriptor)
}
start()
}
/// Starts sending notifications if currently stopped
func start() {
if !state {
state = true
dispatch_resume(source)
}
}
/// Stops sending notifications if currently enabled
func stop() {
if state {
state = false
dispatch_suspend(source)
}
}
deinit {
dispatch_source_cancel(source)
}
}
File diff suppressed because it is too large Load Diff
-630
View File
@@ -1,630 +0,0 @@
//
// SMBTransmitter.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
protocol SMBRequest {
func data() -> NSData
}
protocol SMBResponse {
init? (data: NSData)
}
internal func encode<T>(inout value: T) -> NSData {
return withUnsafePointer(&value) { p in
NSData(bytes: p, length: sizeofValue(value))
}
}
internal func decode<T>(data: NSData) -> T {
let pointer = UnsafeMutablePointer<T>.alloc(sizeof(T.Type))
data.getBytes(pointer, length: sizeof(T.Type))
return pointer.move()
}
// 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 {
var currentMessageID: 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
}
func sessionSetupForSMB2() -> SMB2.SessionSetupResponse? {
return nil
}
func messageId() -> UInt64 {
defer {
currentMessageID += 1
}
return currentMessageID
}
// MARK: create and analyse messages
class 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?)]) {
guard data.length > 30 else {
throw NSURLError.BadServerResponse
}
var buffer = [UInt8](count: data.length, repeatedValue: 0)
guard determineSMBVersion(data) == 1 else {
throw SMBFileProviderError.IncompatibleHeader
}
let headersize = sizeof(SMB1.Header.self)
let header: SMB1.Header = decode(data)
var blocks = [(params: [UInt16], message: NSData?)]()
var offset = headersize
while offset < data.length {
let paramWords: [UInt16]
let paramWordsCount = Int(buffer[offset])
guard data.length > (paramWordsCount * 2 + offset) else {
throw SMBFileProviderError.IncorrectParamsLength
}
offset += sizeof(UInt8)
var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)])
let paramData = NSData(bytesNoCopy: &rawParamWords, length: rawParamWords.count)
paramWords = decode(paramData)
offset += paramWordsCount * 2
let messageBytesCountLittleEndian = [UInt8](buffer[offset...(offset + 1)])
let messageBytesCount = Int(UnsafePointer<UInt16>(messageBytesCountLittleEndian).memory)
offset += sizeof(UInt16)
guard data.length >= (offset + messageBytesCount) else {
throw SMBFileProviderError.IncorrectMessageLength
}
var rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
offset += messageBytesCount
let message = NSData(bytes: &rawMessage, length: rawMessage.count)
blocks.append((params: paramWords, message: message))
}
return (header, blocks)
}
class func digestSMB2Message(data: NSData) throws -> (header: SMB2.Header, message: SMBResponse?)? {
guard data.length > 65 else {
throw NSURLError.BadServerResponse
}
guard determineSMBVersion(data) == 2 else {
throw SMBFileProviderError.IncompatibleHeader
}
let headersize = sizeof(SMB2.Header.self)
let headerData = data.subdataWithRange(NSRange(location: 0, length: headersize))
let messageSize = data.length - headersize
let messageData = data.subdataWithRange(NSRange(location: headersize, length: messageSize))
let header: SMB2.Header = decode(headerData)
switch header.command {
case .NEGOTIATE:
return (header, SMB2.NegotiateResponse(data: messageData))
case .SESSION_SETUP:
return (header, SMB2.SessionSetupResponse(data: messageData))
case .LOGOFF:
return (header, SMB2.LogOff(data: messageData))
case .TREE_CONNECT:
return (header, SMB2.TreeConnectResponse(data: messageData))
case .TREE_DISCONNECT:
return (header, SMB2.TreeDisconnect(data: messageData))
case .CREATE:
return (header, SMB2.CreateResponse(data: messageData))
case .CLOSE:
return (header, SMB2.CloseResponse(data: messageData))
case .FLUSH:
return (header, SMB2.FlushResponse(data: messageData))
case .READ:
return (header, nil) // FIXME:
case .WRITE:
return (header, nil) // FIXME:
case .LOCK:
return (header, nil) // FIXME:
case .IOCTL:
return (header, nil)
case .CANCEL:
return (header, nil)
case .ECHO:
return (header, SMB2.Echo(data: messageData))
case .QUERY_DIRECTORY:
return (header, nil) // FIXME:
case .CHANGE_NOTIFY:
return (header, nil) // FIXME:
case .QUERY_INFO:
return (header, nil) // FIXME:
case .SET_INFO:
return (header, nil) // FIXME:
case .OPLOCK_BREAK:
return (header, nil) // FIXME:
case .INVALID:
throw SMBFileProviderError.InvalidCommand
}
}
class func createSMBMessage(header: SMB1.Header, blocks: [(params: NSData?, message: NSData?)]) -> NSData {
var headerv = header
let result = NSMutableData(data: encode(&headerv))
for block in blocks {
var paramWordsCount = UInt8(block.params?.length ?? 0)
result.appendBytes(&paramWordsCount, length: sizeofValue(paramWordsCount))
if let params = block.params {
result.appendData(params)
}
var messageLen = UInt16(block.message?.length ?? 0)
result.appendBytes(&messageLen, length: sizeofValue(messageLen))
if let message = block.message {
result.appendData(message)
}
}
return result
}
class func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> NSData {
var headerv = header
let result = NSMutableData(data: encode(&headerv))
result.appendData(message.data())
return result
}
}
struct SMBTime {
var time: UInt64
init(time: UInt64) {
self.time = time
}
init(unixTime: UInt) {
self.time = (UInt64(unixTime) + 11644473600) * 10000000
}
init(timeIntervalSince1970: NSTimeInterval) {
self.time = UInt64((timeIntervalSince1970 + 11644473600) * 10000000)
}
init(date: NSDate) {
self.init(timeIntervalSince1970: date.timeIntervalSince1970)
}
var unixTime: UInt {
return UInt(self.time / 10000000 - 11644473600)
}
var date: NSDate {
return NSDate(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600)
}
}
protocol FileProviderSMBHeader {
var protocolID: UInt32 { get }
static var protocolConst: UInt32 { get }
}
// SMB/CIFS Types
struct SMB1 {
struct Header { // 32 bytes
// header is always \u{ff}SMB
let protocolID: UInt32
static let protocolConst: UInt32 = 0x424d53ff
private var _command: UInt8
var command: Command {
get {
return Command(rawValue: _command) ?? .INVALID
}
set {
_command = newValue.rawValue
}
}
// error messages from the server to the client
private var _status: (UInt8, UInt8, UInt8, UInt8)
var error: (Class: UInt8, code: UInt16) {
get {
return (_status.0, UInt16(_status.2) + (UInt16(_status.3) << 8))
}
set {
_status = (newValue.Class, 0, UInt8(newValue.code & 0xff), UInt8(newValue.code >> 8))
}
}
var ntStatus: UInt32 {
get {
let statusLo = UInt32(_status.0) + UInt32(_status.1) << 8
let statusHi = UInt32(_status.2) + UInt32(_status.3) << 8
return statusHi << 16 + statusLo
}
set {
_status = (UInt8(newValue & 0xff), UInt8(newValue >> 8 & 0xff), UInt8(newValue >> 16 & 0xff), UInt8(newValue >> 24 & 0xff))
}
}
var flags: Flags
var flags2: Flags2
var pidHigh: UInt16
// encryption key used for validating messages over connectionless transports
private var _securityKey: (UInt16, UInt16)
var securityKey: UInt32 {
get {
return UInt32(_securityKey.1) << 16 + UInt32(_securityKey.0)
}
set {
_securityKey = (UInt16(newValue & 0xffff), UInt16(newValue >> 16))
}
}
/// Connection identifier
var securityCID: UInt16
/// Identifier of the sequence of a message over connectionless transports
var securitySequenceNumber: UInt16
private var ununsed: UInt16
var treeId: UInt16
var pidLow: UInt16
var userId: UInt16
var multiplexId: UInt16
var pid: UInt32 {
get {
return UInt32(pidLow) + UInt32(pidHigh) << 16
}
set {
pidLow = UInt16(newValue & 0xffff)
pidHigh = UInt16(newValue >> 16)
}
}
init(command: Command, ntStatus: UInt32 = 0, flags: Flags, flags2: Flags2 = [.LONG_NAMES, .ERR_STATUS, .UNICODE], securityKey: UInt32 = 0, securityCID: UInt16 = 0, securitySequenceNumber: UInt16 = 0, treeId: UInt16, pid: UInt32, userId: UInt16, multiplexId: UInt16) {
self.protocolID = Header.protocolConst
self._command = command.rawValue
_status = (UInt8(ntStatus & 0xff), UInt8(ntStatus >> 8 & 0xff), UInt8(ntStatus >> 16 & 0xff), UInt8(ntStatus >> 24 & 0xff))
self.flags = flags
self.flags2 = flags2
self._securityKey = (UInt16(securityKey & 0xffff), UInt16(securityKey >> 16))
self.securityCID = securityCID
self.securitySequenceNumber = securitySequenceNumber
self.ununsed = 0
self.treeId = treeId
self.pidLow = UInt16(pid & 0xffff)
self.pidHigh = UInt16(pid >> 16)
self.userId = userId
self.multiplexId = multiplexId
}
}
struct Flags: OptionSetType {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
/** This bit is set (1) in the NEGOTIATE (0x72) Response if the server supports
* LOCK_AND_READ (0x13) and WRITE_AND_UNLOCK (0x14) commands. */
static let LOCK_AND_READ_OK = Flags(rawValue: 0x01)
static let BUF_AVAIL = Flags(rawValue: 0x02)
static let CASE_INSENSITIVE = Flags(rawValue: 0x08)
static let CANONICALIZED_PATHS = Flags(rawValue: 0x10)
static let OPLOCK = Flags(rawValue: 0x20)
static let OPBATCH = Flags(rawValue: 0x40)
/** When on, this message is being sent from the server in response to a client request. */
static let REPLY = Flags(rawValue: 0x80)
}
struct Flags2: OptionSetType {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
/** Client: the message MAY contain long file names. */
static let LONG_NAMES = Flags2(rawValue: 0x0001)
/** Client: the client is aware of extended attributes (EAs). */
static let EAS = Flags2(rawValue: 0x0002)
/** Client: the client is requesting signing (if signing is not yet active) or the message
* being sent is signed. This bit is used on the SMB header of an SESSION_SETUP_ANDX */
static let SMB_SECURITY_SIGNATURE = Flags2(rawValue: 0x0004)
/// Reserved but not implemented.
static let IS_LONG_NAME = Flags2(rawValue: 0x0040)
/** client aware of Extended Security negotiation */
static let EXT_SEC = Flags2(rawValue: 0x0800)
/** any pathnames in this SMB SHOULD be resolved in the Distributed File System (DFS) */
static let DFS = Flags2(rawValue: 0x1000)
/**
* This flag is useful only on a read request. If the bit is set, then the client MAY read
* the file if the client does not have read permission but does have execute permission. */
static let PAGING_IO = Flags2(rawValue: 0x2000) // READ_IF_EXECUTE
/**
* Client: the server MUST return errors as 32-bit NTSTATUS codes in the response
* Server: the Status field in the header is formatted as an NTSTATUS cod */
static let ERR_STATUS = Flags2(rawValue: 0x4000)
/**
* Each field that contains a string in this SMB message MUST be encoded
* as an array of 16-bit Unicode characters */
static let UNICODE = Flags2(rawValue: 0x8000)
}
enum Command: UInt8 {
case CREATE_DIRECTORY = 0x00
case DELETE_DIRECTORY = 0x01
case OPEN = 0x02
case CREATE = 0x03
case CLOSE = 0x04
case FLUSH = 0x05
case DELETE = 0x06
case RENAME = 0x07
case QUERY_INFORMATION = 0x08
case SET_INFORMATION = 0x09
case READ = 0x0A
case WRITE = 0x0B
case LOCK_BYTE_RANGE = 0x0C
case UNLOCK_BYTE_RANGE = 0x0D
case CREATE_TEMPORARY = 0x0E
case CREATE_NEW = 0x0F
case CHECK_DIRECTORY = 0x10
case PROCESS_EXIT = 0x11
case SEEK = 0x12
case LOCK_AND_READ = 0x13
case WRITE_AND_UNLOCK = 0x14
case READ_RAW = 0x1A
case READ_MPX = 0x1B
case READ_MPX_SECONDARY = 0x1C
case WRITE_RAW = 0x1D
case WRITE_MPX = 0x1E
case WRITE_COMPLETE = 0x20
case SET_INFORMATION2 = 0x22
case QUERY_INFORMATION2 = 0x23
case LOCKING_ANDX = 0x24
case TRANSACTION = 0x25
case TRANSACTION_SECONDARY = 0x26
case IOCTL = 0x27
case IOCTL_SECONDARY = 0x28
case COPY = 0x29
case MOVE = 0x2A
case ECHO = 0x2B
case WRITE_AND_CLOSE = 0x2C
case OPEN_ANDX = 0x2D
case READ_ANDX = 0x2E
case WRITE_ANDX = 0x2F
case CLOSE_AND_TREE_DISC = 0x31
case TRANSACTION2 = 0x32
case TRANSACTION2_SECONDARY = 0x33
case FIND_CLOSE2 = 0x34
case FIND_NOTIFY_CLOSE = 0x35
case TREE_CONNECT = 0x70
case TREE_DISCONNECT = 0x71
case NEGOTIATE = 0x72
case SESSION_SETUP_ANDX = 0x73
case LOGOFF_ANDX = 0x74
case TREE_CONNECT_ANDX = 0x75
case QUERY_INFORMATION_DISK = 0x80
case SEARCH = 0x81
case FIND = 0x82
case FIND_UNIQUE = 0x83
case NT_TRANSACT = 0xA0
case NT_TRANSACT_SECONDARY = 0xA1
case NT_CREATE_ANDX = 0xA2
case NT_CANCEL = 0xA4
case OPEN_PRINT_FILE = 0xC0
case WRITE_PRINT_FILE = 0xC1
case CLOSE_PRINT_FILE = 0xC2
case GET_PRINT_QUEUE = 0xC3
case READ_BULK = 0xD8
case WRITE_BULK = 0xD9
case WRITE_BULK_DATA = 0xDA
case INVALID = 0xFE
}
}
/// Error Types and Description
enum NTStatus: UInt32, ErrorType, CustomStringConvertible {
case SUCCESS = 0x00000000
case NOT_IMPLEMENTED = 0xC0000002
case INVALID_DEVICE_REQUEST = 0xC0000010
case ILLEGAL_FUNCTION = 0xC00000AF
case NO_SUCH_FILE = 0xC000000F
case NO_SUCH_DEVICE = 0xC000000E
case OBJECT_NAME_NOT_FOUND = 0xC0000034
case OBJECT_PATH_INVALID = 0xC0000039
case OBJECT_PATH_NOT_FOUND = 0xC000003A
case OBJECT_PATH_SYNTAX_BAD = 0xC000003B
case DFS_EXIT_PATH_FOUND = 0xC000009B
case REDIRECTOR_NOT_STARTED = 0xC00000FB
case TOO_MANY_OPENED_FILES = 0xC000011F
case ACCESS_DENIED = 0xC0000022
case INVALID_LOCK_SEQUENCE = 0xC000001E
case INVALID_VIEW_SIZE = 0xC000001F
case ALREADY_COMMITTED = 0xC0000021
case PORT_CONNECTION_REFUSED = 0xC0000041
case THREAD_IS_TERMINATING = 0xC000004B
case DELETE_PENDING = 0xC0000056
case PRIVILEGE_NOT_HELD = 0xC0000061
case LOGON_FAILURE = 0xC000006D
case FILE_IS_A_DIRECTORY = 0xC00000BA
case FILE_RENAMED = 0xC00000D5
case PROCESS_IS_TERMINATING = 0xC000010A
case DIRECTORY_NOT_EMPTY = 0xC0000101
case CANNOT_DELETE = 0xC0000121
case FILE_NOT_AVAILABLE = 0xC0000467
case FILE_DELETED = 0xC0000123
case SMB_BAD_FID = 0x00060001
case INVALID_HANDLE = 0xC0000008
case OBJECT_TYPE_MISMATCH = 0xC0000024
case PORT_DISCONNECTED = 0xC0000037
case INVALID_PORT_HANDLE = 0xC0000042
case FILE_CLOSED = 0xC0000128
case HANDLE_NOT_CLOSABLE = 0xC0000235
case SECTION_TOO_BIG = 0xC0000040
case TOO_MANY_PAGING_FILES = 0xC0000097
case INSUFF_SERVER_RESOURCES = 0xC0000205
case OS2_INVALID_ACCESS = 0x000C0001
case ACCESS_DENIED_2 = 0xC00000CA
case DATA_ERROR = 0xC000009C
case NOT_SAME_DEVICE = 0xC00000D4
case NO_MORE_FILES = 0x80000006
case NO_MORE_ENTRIES = 0x8000001A
case UNSUCCESSFUL = 0xC0000001
case SHARING_VIOLATION = 0xC0000043
case FILE_LOCK_CONFLICT = 0xC0000054
case LOCK_NOT_GRANTED = 0xC0000055
case END_OF_FILE = 0xC0000011
case NOT_SUPPORTED = 0xC00000BB
case OBJECT_NAME_COLLISION = 0xC0000035
case INVALID_PARAMETER = 0xC000000D
case OS2_INVALID_LEVEL = 0x007C0001
case OS2_NEGATIVE_SEEK = 0x00830001
case RANGE_NOT_LOCKED = 0xC000007E
case OS2_NO_MORE_SIDS = 0x00710001
case OS2_CANCEL_VIOLATION = 0x00AD0001
case OS2_ATOMIC_LOCKS_NOT_SUPPORTED = 0x00AE0001
case INVALID_INFO_CLASS = 0xC0000003
case INVALID_PIPE_STATE = 0xC00000AD
case INVALID_READ_MODE = 0xC00000B4
case OS2_CANNOT_COPY = 0x010A0001
case STOPPED_ON_SYMLINK = 0x8000002D
case INSTANCE_NOT_AVAILABLE = 0xC00000AB
case PIPE_NOT_AVAILABLE = 0xC00000AC
case PIPE_BUSY = 0xC00000AE
case PIPE_CLOSING = 0xC00000B1
case PIPE_EMPTY = 0xC00000D9
case PIPE_DISCONNECTED = 0xC00000B0
case BUFFER_OVERFLOW = 0x80000005
case MORE_PROCESSING_REQUIRED = 0xC0000016
case EA_TOO_LARGE = 0xC0000050
case OS2_EAS_DIDNT_FIT = 0x01130001
case EAS_NOT_SUPPORTED = 0xC000004F
case EA_LIST_INCONSISTENT = 0x80000014
case OS2_EA_ACCESS_DENIED = 0x03E20001
case NOTIFY_ENUM_DIR = 0x0000010C
case INVALID_SMB = 0x00010002
case WRONG_PASSWORD = 0xC000006A
case PATH_NOT_COVERED = 0xC0000257
case NETWORK_NAME_DELETED = 0xC00000C9
case SMB_BAD_TID = 0x00050002
case BAD_NETWORK_NAME = 0xC00000CC
case BAD_DEVICE_TYPE = 0xC00000CB
case SMB_BAD_COMMAND = 0x00160002
case PRINT_QUEUE_FULL = 0xC00000C6
case NO_SPOOL_SPACE = 0xC00000C7
case PRINT_CANCELLED = 0xC00000C8
case UNEXPECTED_NETWORK_ERROR = 0xC00000C4
case IO_TIMEOUT = 0xC00000B5
case REQUEST_NOT_ACCEPTED = 0xC00000D0
case TOO_MANY_SESSIONS = 0xC00000CE
case SMB_BAD_UID = 0x005B0002
case SMB_USE_MPX = 0x00FA0002
case SMB_USE_STANDARD = 0x00FB0002
case SMB_CONTINUE_MPX = 0x00FC0002
case ACCOUNT_DISABLED = 0xC0000072
case ACCOUNT_EXPIRED = 0xC0000193
case INVALID_WORKSTATION = 0xC0000070
case INVALID_LOGON_HOURS = 0xC000006F
case PASSWORD_EXPIRED = 0xC0000071
case PASSWORD_MUST_CHANGE = 0xC0000224
case SMB_NO_SUPPORT = 0xFFFF0002
case MEDIA_WRITE_PROTECTED = 0xC00000A2
case NO_MEDIA_IN_DEVICE = 0xC0000013
case INVALID_DEVICE_STATE = 0xC0000184
case DATA_ERROR_2 = 0xC000003E
case CRC_ERROR = 0xC000003F
case DISK_CORRUPT_ERROR = 0xC0000032
case NONEXISTENT_SECTOR = 0xC0000015
case DEVICE_PAPER_EMPTY = 0x8000000E
case WRONG_VOLUME = 0xC0000012
case DISK_FULL = 0xC000007F
case BUFFER_TOO_SMALL = 0xC0000023
case BAD_IMPERSONATION_LEVEL = 0xC00000A5
case USER_SESSION_DELETED = 0xC0000203
case NETWORK_SESSION_EXPIRED = 0xC000035C
case SMB_TOO_MANY_UIDS = 0xC000205A
var description: String {
switch self {
case NOT_IMPLEMENTED, INVALID_DEVICE_REQUEST, ILLEGAL_FUNCTION:
return "Invalid Function."
case NO_SUCH_FILE, NO_SUCH_DEVICE, OBJECT_NAME_NOT_FOUND:
return "File not found."
case OBJECT_PATH_INVALID, OBJECT_PATH_NOT_FOUND, OBJECT_PATH_SYNTAX_BAD, DFS_EXIT_PATH_FOUND, REDIRECTOR_NOT_STARTED:
return "A component in the path prefix is not a directory."
case TOO_MANY_OPENED_FILES:
return "Too many open files. No FIDs are available."
case ACCESS_DENIED, INVALID_LOCK_SEQUENCE, INVALID_VIEW_SIZE, ALREADY_COMMITTED, PORT_CONNECTION_REFUSED, THREAD_IS_TERMINATING, DELETE_PENDING, PRIVILEGE_NOT_HELD, LOGON_FAILURE, FILE_IS_A_DIRECTORY, FILE_RENAMED, PROCESS_IS_TERMINATING, CANNOT_DELETE, FILE_DELETED:
return "Access denied."
case SMB_BAD_FID, INVALID_HANDLE, OBJECT_TYPE_MISMATCH, PORT_DISCONNECTED, INVALID_PORT_HANDLE, FILE_CLOSED, HANDLE_NOT_CLOSABLE:
return "Invalid FID."
case SECTION_TOO_BIG, TOO_MANY_PAGING_FILES, INSUFF_SERVER_RESOURCES:
return "Insufficient server memory to perform the requested operation."
case OS2_INVALID_ACCESS:
return "Invalid open mode."
case DATA_ERROR:
return "Bad data. (May be generated by IOCTL calls on the server.)"
case DIRECTORY_NOT_EMPTY:
return "Remove of directory failed because it was not empty."
case NOT_SAME_DEVICE:
return "A file system operation (such as a rename) across two devices was attempted."
case NO_MORE_FILES:
return "No (more) files found following a file search command."
case UNSUCCESSFUL:
return "General error."
case SHARING_VIOLATION:
return "Sharing violation. A requested open mode conflicts with the sharing mode of an existing file handle."
case FILE_LOCK_CONFLICT, LOCK_NOT_GRANTED:
return "A lock request specified an invalid locking mode, or conflicted with an existing file lock."
case END_OF_FILE:
return "Attempted to read beyond the end of the file."
case NOT_SUPPORTED:
return "This command is not supported by the server."
case OBJECT_NAME_COLLISION:
return "An attempt to create a file or directory failed because an object with the same pathname already exists."
case INVALID_PARAMETER:
return "A parameter supplied with the message is invalid."
case OS2_INVALID_LEVEL:
return "Invalid information level."
case OS2_NEGATIVE_SEEK:
return "An attempt was made to seek to a negative absolute offset within a file."
case RANGE_NOT_LOCKED:
return "The byte range specified in an unlock request was not locked."
case OS2_NO_MORE_SIDS:
return "Maximum number of searches has been exhausted."
case OS2_CANCEL_VIOLATION:
return "No lock request was outstanding for the supplied cancel region."
case OS2_ATOMIC_LOCKS_NOT_SUPPORTED:
return "The file system does not support atomic changes to the lock type."
case INVALID_INFO_CLASS, INVALID_PIPE_STATE, INVALID_READ_MODE:
return "Invalid named pipe."
case OS2_CANNOT_COPY:
return "The copy functions cannot be used."
case INSTANCE_NOT_AVAILABLE, PIPE_NOT_AVAILABLE, PIPE_BUSY:
return "All instances of the designated named pipe are busy."
case PIPE_CLOSING, PIPE_EMPTY:
return "The designated named pipe is in the process of being closed."
case PIPE_DISCONNECTED:
return "The designated named pipe exists, but there is no server process listening on the server side."
case BUFFER_OVERFLOW, MORE_PROCESSING_REQUIRED:
return "There is more data available to read on the designated named pipe."
case EA_TOO_LARGE, OS2_EAS_DIDNT_FIT:
return "Either there are no extended attributes, or the available extended attributes did not fit into the response."
case EAS_NOT_SUPPORTED:
return "The server file system does not support Extended Attributes."
case OS2_EA_ACCESS_DENIED:
return "Access to the extended attribute was denied."
default:
return ""
}
}
}
-121
View File
@@ -1,121 +0,0 @@
//
// SambaFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
class SMBFileProvider: FileProvider, FileProviderMonitor {
var type: String = "Samba"
var isPathRelative: Bool = true
var baseURL: NSURL?
var currentPath: String = ""
var dispatch_queue: dispatch_queue_t
weak var delegate: FileProviderDelegate?
let credential: NSURLCredential?
typealias FileObjectClass = FileObject
init? (baseURL: NSURL, credential: NSURLCredential, afterInitialized: SimpleCompletionHandler) {
guard baseURL.scheme.lowercaseString == "smb" else {
return nil
}
self.baseURL = baseURL
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
//let url = baseURL.absoluteString
self.credential = credential
}
func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObjectClass], error: ErrorType?) -> Void)) {
NotImplemented()
dispatch_async(dispatch_queue) {
}
}
func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObjectClass?, error: ErrorType?) -> Void)) {
NotImplemented()
}
weak var fileOperationDelegate: FileOperationDelegate?
func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
NotImplemented()
}
func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
NotImplemented()
}
func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObjectClass) -> Void)?, completionHandler: ((files: [FileObjectClass], error: ErrorType?) -> Void)) {
NotImplemented()
}
func registerNotifcation(path: String, eventHandler: (() -> Void)) {
NotImplemented()
}
func unregisterNotifcation(path: String) {
NotImplemented()
}
func isRegisteredForNotification(path: String) -> Bool {
return false
}
}
// MARK: basic CIFS interactivity
enum SMBFileProviderError: Int, ErrorType, CustomStringConvertible {
case BadHeader
case IncompatibleHeader
case IncorrectParamsLength
case IncorrectMessageLength
case InvalidCommand
var description: String {
return "SMB message structure is invalid"
}
}
extension SMBFileProvider {
private func getPID() -> UInt32 {
return UInt32(NSProcessInfo.processInfo().processIdentifier)
}
}
-249
View File
@@ -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
*/
init?(baseURL: NSURL, secure: Bool = false) {
self.baseURL = baseURL
self.secureConnection = secure
let scheme = baseURL.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
}
}
-553
View File
@@ -1,553 +0,0 @@
//
// WebDAVFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
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 {
let code: FileProviderWebDavErrorCode
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 {
let contentType: String
let entryTag: String?
init(absoluteURL: NSURL, name: String, path: String, size: Int64, contentType: String, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, entryTag: String?) {
self.contentType = contentType
self.entryTag = entryTag
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
}
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
// in case of using this class with unencrypted HTTP connection.
public class WebDAVFileProvider: NSObject, FileProviderBasic {
public let type: String = "WebDAV"
public let isPathRelative: Bool = true
public let baseURL: NSURL?
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
}
}
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential?
private var _session: NSURLSession?
private var session: NSURLSession {
if _session == nil {
let queue = NSOperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: queue)
}
return _session!
}
init? (baseURL: NSURL, credential: NSURLCredential?) {
if !["http", "https"].contains(baseURL.scheme.lowercaseString) {
return nil
}
self.baseURL = baseURL
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
//let url = baseURL.absoluteString
self.credential = credential
}
deinit {
_session?.invalidateAndCancel()
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void)) {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PROPFIND"
request.setValue(baseURL?.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
if let data = data {
let xresponse = self.parseXMLResponse(data)
var fileObjects = [WebDavFileObject]()
for attr in xresponse {
if attr.href.path == url.path {
continue
}
fileObjects.append(self.mapToFileObject(attr))
}
completionHandler(contents: fileObjects, error: error)
return
}
completionHandler(contents: [], error: error)
}
task.resume()
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
let request = NSMutableURLRequest(URL: absoluteURL(path))
request.HTTPMethod = "PROPFIND"
request.setValue(baseURL?.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
if let data = data {
let xresponse = self.parseXMLResponse(data)
if let attr = xresponse.first {
completionHandler(attributes: self.mapToFileObject(attr), error: error)
return
}
}
completionHandler(attributes: nil, error: error)
}
task.resume()
}
public weak var fileOperationDelegate: FileOperationDelegate?
}
extension WebDAVFileProvider: FileProviderOperations {
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let url = absoluteURL((atPath as NSString).stringByAppendingPathComponent(folderName) + "/")
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "MKCOL"
request.setValue(baseURL?.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 {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
return
}
completionHandler?(error: error)
self.delegateNotify(.Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"), error: error)
}
task.resume()
}
public func createFile(fileAttribs: FileObject, atPath path: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
let request = NSMutableURLRequest(URL: absoluteURL(path))
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)
}
task.taskDescription = self.dictionaryToJSON(["type": "Create", "source": (path as NSString).stringByAppendingPathComponent(fileAttribs.name)])
task.resume()
}
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "MOVE"
request.setValue(baseURL?.absoluteString, forHTTPHeaderField: "Host")
request.setValue(absoluteURL(path).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) {
defer {
self.delegateNotify(.Move(source: path, destination: toPath), error: error)
}
if code == .MultiStatus, let data = data {
let xresponses = self.parseXMLResponse(data)
for xresponse in xresponses {
if xresponse.status >= 300 {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
}
} else {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error: error)
}
task.resume()
}
public func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "COPY"
request.setValue(baseURL?.absoluteString, forHTTPHeaderField: "Host")
request.setValue(absoluteURL(path).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) {
defer {
self.delegateNotify(.Copy(source: path, destination: toPath), error: error)
}
if code == .MultiStatus, let data = data {
let xresponses = self.parseXMLResponse(data)
for xresponse in xresponses {
if xresponse.status >= 300 {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
}
} else {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error: error)
}
task.resume()
}
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "DELETE"
request.setValue(baseURL?.absoluteString, forHTTPHeaderField: "Host")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse, let code = FileProviderWebDavErrorCode(rawValue: response.statusCode) {
defer {
self.delegateNotify(.Remove(path: path), error: error)
}
if code == .MultiStatus, let data = data {
let xresponses = self.parseXMLResponse(data)
for xresponse in xresponses {
if xresponse.status >= 300 {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
}
} else {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error: error)
}
task.resume()
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
let request = NSMutableURLRequest(URL: absoluteURL(toPath))
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromFile: localFile) { (data, response, error) in
completionHandler?(error: error)
self.delegateNotify(.Move(source: localFile.absoluteString, destination: toPath), error: error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": localFile.absoluteString, "dest": toPath])
task.resume()
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
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
}
}
completionHandler?(error: error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.absoluteString])
task.resume()
}
}
extension WebDAVFileProvider: FileProviderReadWrite {
public func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
let request = NSMutableURLRequest(URL: absoluteURL(path))
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request) { (data, response, error) in
completionHandler(contents: data, error: error)
}
task.resume()
}
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let request = NSMutableURLRequest(URL: absoluteURL(path))
request.HTTPMethod = "GET"
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
completionHandler(contents: data, error: error)
}
task.resume()
}
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: lock destination before writing process
let url = atomically ? absoluteURL(path).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()
}
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PROPFIND"
request.setValue(baseURL?.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
if let data = data {
let xresponse = self.parseXMLResponse(data)
var fileObjects = [WebDavFileObject]()
for attr in xresponse {
if let path = attr.href.path where !((path as NSString).lastPathComponent.containsString(query)) {
continue
}
let fileObject = self.mapToFileObject(attr)
fileObjects.append(fileObject)
foundItemHandler?(fileObject)
}
completionHandler(files: fileObjects, error: error)
return
}
completionHandler(files: [], error: error)
}
task.resume()
}
private func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is no unified api for monitoring WebDAV server content change/update
* Microsoft Exchange uses SUBSCRIBE method, Apple uses push notification system.
* while both is unavailable in a mobile platform.
* A messy approach is listing a directory with an interval period and compare
* with previous results
*/
}
private func unregisterNotifcation(path: String) {
}
// TODO: implements methods for lock mechanism
}
// MARK: WEBDAV XML response implementation
internal extension WebDAVFileProvider {
struct DavResponse {
let href: NSURL
let hrefString: String
let status: Int?
let prop: [String: String]
}
private func parseXMLResponse(response: NSData) -> [DavResponse] {
var result = [DavResponse]()
do {
let xml = try AEXMLDocument(xmlData: response)
var rootnode = xml.root
var responsetag = "response"
for node in rootnode.all ?? [] {
if node.name.lowercaseString.hasSuffix("multistatus") {
rootnode = node
}
}
for node in rootnode.children ?? [] {
if node.name.lowercaseString.hasSuffix("response") {
responsetag = node.name
break
}
}
for responseNode in rootnode[responsetag].all ?? [] {
var hreftag = "href"
var statustag = "status"
var propstattag = "propstat"
for node in responseNode.children ?? [] {
if node.name.lowercaseString.hasSuffix("href") {
hreftag = node.name
}
if node.name.lowercaseString.hasSuffix("status") {
statustag = node.name
}
if node.name.lowercaseString.hasSuffix("propstat") {
propstattag = node.name
}
}
let href = responseNode[hreftag].value
if let href = href, hrefURL = NSURL(string: href) {
var status: Int?
let statusDesc = (responseNode[statustag].stringValue).componentsSeparatedByString(" ")
if statusDesc.count > 2 {
status = Int(statusDesc[1])
}
var propDic = [String: String]()
let propStatNode = responseNode[propstattag]
for node in propStatNode.children ?? [] {
if node.name.lowercaseString.hasSuffix("status") {
statustag = node.name
break
}
}
let statusDesc2 = (propStatNode[statustag].stringValue).componentsSeparatedByString(" ")
if statusDesc2.count > 2 {
status = Int(statusDesc2[1])
}
var proptag = "prop"
for tnode in propStatNode.children ?? [] {
if tnode.name.lowercaseString.hasSuffix("prop") {
proptag = tnode.name
}
break
}
for propItemNode in propStatNode[proptag].children ?? [] {
propDic[propItemNode.name.componentsSeparatedByString(":").last!.lowercaseString] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") {
if propItemNode.xmlStringCompact.containsString("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
}
}
}
result.append(DavResponse(href: hrefURL, hrefString: href, status: status, prop: propDic))
}
}
} catch _ {
}
return result
}
private func mapToFileObject(davResponse: DavResponse) -> WebDavFileObject {
var href = davResponse.href
if href.baseURL == nil {
href = absoluteURL(href.path ?? "")
}
let name = davResponse.prop["displayname"] ?? (davResponse.hrefString.stringByRemovingPercentEncoding! as NSString).lastPathComponent
let size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
let createdDate = self.resolveRFCDate(davResponse.prop["creationdate"] ?? "")
let modifiedDate = self.resolveRFCDate(davResponse.prop["getlastmodified"] ?? "")
let contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
let isDirectory = contentType == "httpd/unix-directory"
let entryTag = davResponse.prop["getetag"]
return WebDavFileObject(absoluteURL: href, name: name, path: href.path ?? name, size: size, contentType: contentType, createdDate: createdDate, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isHidden: false, isReadOnly: false, entryTag: entryTag)
}
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)
}
})
}
}
// MARK: URLSession delegate
extension WebDAVFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
return
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperation
switch type {
case "Create":
op = .Create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .Copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .Move(source: source, destination: dest)
case "Modify":
op = .Modify(path: source)
case "Remove":
op = .Remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .Link(link: source, target: dest)
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.delegate?.fileproviderProgress(self, operation: op, progress: progress)
}
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, dest = json["dest"] as? String else {
return
}
self.delegate?.fileproviderProgress(self, operation: .Copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
}
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
}
}
+29
View File
@@ -0,0 +1,29 @@
//
// AEXML.h
//
// Copyright (c) 2014 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT double AEXMLVersionNumber;
FOUNDATION_EXPORT const unsigned char AEXMLVersionString[];
+128
View File
@@ -0,0 +1,128 @@
//
// Document.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class is inherited from `AEXMLElement` and has a few addons to represent **XML Document**.
XML Parsing is also done with this object.
*/
open class AEXMLDocument: AEXMLElement {
// MARK: - Properties
/// Root (the first child element) element of XML Document **(Empty element with error if not exists)**.
open var root: AEXMLElement {
guard let rootElement = children.first else {
let errorElement = AEXMLElement(name: "Error")
errorElement.error = AEXMLError.rootElementMissing
return errorElement
}
return rootElement
}
open let options: AEXMLOptions
// MARK: - Lifecycle
/**
Designated initializer - Creates and returns new XML Document object.
- parameter root: Root XML element for XML Document (defaults to `nil`).
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object.
*/
public init(root: AEXMLElement? = nil, options: AEXMLOptions = AEXMLOptions()) {
self.options = options
let documentName = String(describing: AEXMLDocument.self)
super.init(name: documentName)
// document has no parent element
parent = nil
// add root element to document (if any)
if let rootElement = root {
_ = addChild(rootElement)
}
}
/**
Convenience initializer - used for parsing XML data (by calling `loadXMLData:` internally).
- parameter xmlData: XML data to parse.
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(xml: Data, options: AEXMLOptions = AEXMLOptions()) throws {
self.init(options: options)
try loadXML(xml)
}
/**
Convenience initializer - used for parsing XML string (by calling `init(xmlData:options:)` internally).
- parameter xmlString: XML string to parse.
- parameter encoding: String encoding for creating `Data` from `xmlString` (defaults to `String.Encoding.utf8`)
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(xml: String,
encoding: String.Encoding = String.Encoding.utf8,
options: AEXMLOptions = AEXMLOptions()) throws
{
guard let data = xml.data(using: encoding) else { throw AEXMLError.parsingFailed }
try self.init(xml: data, options: options)
}
// MARK: - Parse XML
/**
Creates instance of `AEXMLParser` (private class which is simple wrapper around `XMLParser`)
and starts parsing the given XML data. Throws error if data could not be parsed.
- parameter data: XML which should be parsed.
*/
open func loadXML(_ data: Data) throws {
children.removeAll(keepingCapacity: false)
let xmlParser = AEXMLParser(document: self, data: data)
try xmlParser.parse()
}
// MARK: - Override
/// Override of `xml` property of `AEXMLElement` - it just inserts XML Document header at the beginning.
open override var xml: String {
var xml = "\(options.documentHeader.xmlString)\n"
for child in children {
xml += child.xml
}
return xml
}
}
+285
View File
@@ -0,0 +1,285 @@
//
// Element.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This is base class for holding XML structure.
You can access its structure by using subscript like this: `element["foo"]["bar"]` which would
return `<bar></bar>` element from `<element><foo><bar></bar></foo></element>` XML as an `AEXMLElement` object.
*/
open class AEXMLElement {
// MARK: - Properties
/// Every `AEXMLElement` should have its parent element instead of `AEXMLDocument` which parent is `nil`.
open internal(set) weak var parent: AEXMLElement?
/// Child XML elements.
open internal(set) var children = [AEXMLElement]()
/// XML Element name.
open var name: String
/// XML Element value.
open var value: String?
/// XML Element attributes.
open var attributes: [String : String]
/// Error value (`nil` if there is no error).
open var error: AEXMLError?
/// String representation of `value` property (if `value` is `nil` this is empty String).
open var string: String { return value ?? String() }
/// Boolean representation of `value` property (if `value` is "true" or 1 this is `True`, otherwise `False`).
open var bool: Bool { return string.lowercased() == "true" || Int(string) == 1 ? true : false }
/// Integer representation of `value` property (this is **0** if `value` can't be represented as Integer).
open var int: Int { return Int(string) ?? 0 }
/// Double representation of `value` property (this is **0.00** if `value` can't be represented as Double).
open var double: Double { return Double(string) ?? 0.00 }
// MARK: - Lifecycle
/**
Designated initializer - all parameters are optional.
- parameter name: XML element name.
- parameter value: XML element value (defaults to `nil`).
- parameter attributes: XML element attributes (defaults to empty dictionary).
- returns: An initialized `AEXMLElement` object.
*/
public init(name: String, value: String? = nil, attributes: [String : String] = [String : String]()) {
self.name = name
self.value = value
self.attributes = attributes
}
// MARK: - XML Read
/// The first element with given name **(Empty element with error if not exists)**.
open subscript(key: String) -> AEXMLElement {
guard let
first = children.filter({ $0.name == key }).first
else {
let errorElement = AEXMLElement(name: key)
errorElement.error = AEXMLError.elementNotFound
return errorElement
}
return first
}
/// Returns all of the elements with equal name as `self` **(nil if not exists)**.
open var all: [AEXMLElement]? { return parent?.children.filter { $0.name == self.name } }
/// Returns the first element with equal name as `self` **(nil if not exists)**.
open var first: AEXMLElement? { return all?.first }
/// Returns the last element with equal name as `self` **(nil if not exists)**.
open var last: AEXMLElement? { return all?.last }
/// Returns number of all elements with equal name as `self`.
open var count: Int { return all?.count ?? 0 }
fileprivate func filter(withCondition condition: (AEXMLElement) -> Bool) -> [AEXMLElement]? {
guard let elements = all else { return nil }
var found = [AEXMLElement]()
for element in elements {
if condition(element) {
found.append(element)
}
}
return found.count > 0 ? found : nil
}
/**
Returns all elements with given value.
- parameter value: XML element value.
- returns: Optional Array of found XML elements.
*/
open func all(withValue value: String) -> [AEXMLElement]? {
let found = filter { (element) -> Bool in
return element.value == value
}
return found
}
/**
Returns all elements with given attributes.
- parameter attributes: Dictionary of Keys and Values of attributes.
- returns: Optional Array of found XML elements.
*/
open func all(withAttributes attributes: [String : String]) -> [AEXMLElement]? {
let found = filter { (element) -> Bool in
var countAttributes = 0
for (key, value) in attributes {
if element.attributes[key] == value {
countAttributes += 1
}
}
return countAttributes == attributes.count
}
return found
}
// MARK: - XML Write
/**
Adds child XML element to `self`.
- parameter child: Child XML element to add.
- returns: Child XML element with `self` as `parent`.
*/
@discardableResult open func addChild(_ child: AEXMLElement) -> AEXMLElement {
child.parent = self
children.append(child)
return child
}
/**
Adds child XML element to `self`.
- parameter name: Child XML element name.
- parameter value: Child XML element value (defaults to `nil`).
- parameter attributes: Child XML element attributes (defaults to empty dictionary).
- returns: Child XML element with `self` as `parent`.
*/
@discardableResult open func addChild(name: String,
value: String? = nil,
attributes: [String : String] = [String : String]()) -> AEXMLElement
{
let child = AEXMLElement(name: name, value: value, attributes: attributes)
return addChild(child)
}
/// Removes `self` from `parent` XML element.
open func removeFromParent() {
parent?.removeChild(self)
}
fileprivate func removeChild(_ child: AEXMLElement) {
if let childIndex = children.index(where: { $0 === child }) {
children.remove(at: childIndex)
}
}
fileprivate var parentsCount: Int {
var count = 0
var element = self
while let parent = element.parent {
count += 1
element = parent
}
return count
}
fileprivate func indent(withDepth depth: Int) -> String {
var count = depth
var indent = String()
while count > 0 {
indent += "\t"
count -= 1
}
return indent
}
/// Complete hierarchy of `self` and `children` in **XML** escaped and formatted String
open var xml: String {
var xml = String()
// open element
xml += indent(withDepth: parentsCount - 1)
xml += "<\(name)"
if attributes.count > 0 {
// insert attributes
for (key, value) in attributes {
xml += " \(key)=\"\(value.xmlEscaped)\""
}
}
if value == nil && children.count == 0 {
// close element
xml += " />"
} else {
if children.count > 0 {
// add children
xml += ">\n"
for child in children {
xml += "\(child.xml)\n"
}
// add indentation
xml += indent(withDepth: parentsCount - 1)
xml += "</\(name)>"
} else {
// insert string value and close element
xml += ">\(string.xmlEscaped)</\(name)>"
}
}
return xml
}
/// Same as `xmlString` but without `\n` and `\t` characters
open var xmlCompact: String {
let chars = CharacterSet(charactersIn: "\n\t")
return xml.components(separatedBy: chars).joined(separator: "")
}
}
public extension String {
/// String representation of self with XML special characters escaped.
public var xmlEscaped: String {
// we need to make sure "&" is escaped first. Not doing this may break escaping the other characters
var escaped = replacingOccurrences(of: "&", with: "&amp;", options: .literal)
// replace the other four special characters
let escapeChars = ["<" : "&lt;", ">" : "&gt;", "'" : "&apos;", "\"" : "&quot;"]
for (char, echar) in escapeChars {
escaped = escaped.replacingOccurrences(of: char, with: echar, options: .literal)
}
return escaped
}
}
+37
View File
@@ -0,0 +1,37 @@
//
// Error.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/// A type representing error value that can be thrown or inside `error` property of `AEXMLElement`.
public enum AEXMLError: Error {
/// This will be inside `error` property of `AEXMLElement` when subscript is used for not-existing element.
case elementNotFound
/// This will be inside `error` property of `AEXMLDocument` when there is no root element.
case rootElementMissing
/// `AEXMLDocument` can throw this error on `init` or `loadXMLData` if parsing with `XMLParser` was not successful.
case parsingFailed
}
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
+52
View File
@@ -0,0 +1,52 @@
//
// Options.swift
// AEXML
//
// Created by Marko Tadic on 9/10/16.
// Copyright © 2016 AE. All rights reserved.
//
import Foundation
/// Options used in `AEXMLDocument`
public struct AEXMLOptions {
/// Values used in XML Document header
public struct DocumentHeader {
/// Version value for XML Document header (defaults to 1.0).
public var version = 1.0
/// Encoding value for XML Document header (defaults to "utf-8").
public var encoding = "utf-8"
/// Standalone value for XML Document header (defaults to "no").
public var standalone = "no"
/// XML Document header
public var xmlString: String {
return "<?xml version=\"\(version)\" encoding=\"\(encoding)\" standalone=\"\(standalone)\"?>"
}
}
/// Settings used by `Foundation.XMLParser`
public struct ParserSettings {
/// Parser reports the namespaces and qualified names of elements. (defaults to `false`)
public var shouldProcessNamespaces = false
/// Parser reports the prefixes indicating the scope of namespace declarations. (defaults to `false`)
public var shouldReportNamespacePrefixes = false
/// Parser reports declarations of external entities. (defaults to `false`)
public var shouldResolveExternalEntities = false
}
/// Values used in XML Document header (defaults to `DocumentHeader()`)
public var documentHeader = DocumentHeader()
/// Settings used by `Foundation.XMLParser` (defaults to `ParserSettings()`)
public var parserSettings = ParserSettings()
/// Designated initializer - Creates and returns default `AEXMLOptions`.
public init() {}
}
+101
View File
@@ -0,0 +1,101 @@
//
// Parser.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/// Simple wrapper around `Foundation.XMLParser`.
internal class AEXMLParser: NSObject, XMLParserDelegate {
// MARK: - Properties
let document: AEXMLDocument
let data: Data
var currentParent: AEXMLElement?
var currentElement: AEXMLElement?
var currentValue = String()
var parseError: Error?
// MARK: - Lifecycle
init(document: AEXMLDocument, data: Data) {
self.document = document
self.data = data
currentParent = document
super.init()
}
// MARK: - API
func parse() throws {
let parser = XMLParser(data: data)
parser.delegate = self
parser.shouldProcessNamespaces = document.options.parserSettings.shouldProcessNamespaces
parser.shouldReportNamespacePrefixes = document.options.parserSettings.shouldReportNamespacePrefixes
parser.shouldResolveExternalEntities = document.options.parserSettings.shouldResolveExternalEntities
let success = parser.parse()
if !success {
guard let error = parseError else { throw AEXMLError.parsingFailed }
throw error
}
}
// MARK: - XMLParserDelegate
@objc func parser(_ parser: XMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [String : String])
{
currentValue = String()
currentElement = currentParent?.addChild(name: elementName, attributes: attributeDict)
currentParent = currentElement
}
@objc func parser(_ parser: XMLParser, foundCharacters string: String) {
currentValue += string
let newValue = currentValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
currentElement?.value = newValue == String() ? nil : newValue
}
@objc func parser(_ parser: XMLParser,
didEndElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?)
{
currentParent = currentParent?.parent
currentElement = nil
}
@objc func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
self.parseError = parseError
}
}
+346
View File
@@ -0,0 +1,346 @@
//
// DropboxFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
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.
open class DropboxFileProvider: NSObject, FileProviderBasic {
open static let type: String = "WebDAV"
open let isPathRelative: Bool = true
open let baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
}
}
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
fileprivate var _session: URLSession?
fileprivate var sessionDelegate: SessionDelegate?
internal var session: URLSession {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = OperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = URLSession(configuration: URLSessionConfiguration.default, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: queue)
}
return _session!
}
public init? (credential: URLCredential?) {
self.baseURL = nil
dispatch_queue = DispatchQueue(label: "FileProvider.\(DropboxFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.credential = credential
}
deinit {
_session?.invalidateAndCancel()
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
list(path) { (contents, cursor, error) in
completionHandler(contents, error)
}
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestDictionary = ["path": correctPath(path)! as NSString]
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let response = response as? HTTPURLResponse {
defer {
self.delegateNotify(FileOperation.create(path: path), error: error)
}
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8)) : nil
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8), let json = jsonToDictionary(jsonStr), let file = self.mapToFileObject(json) {
completionHandler(file, dbError)
return
}
completionHandler(nil, dbError)
return
}
completionHandler(nil, error)
})
task.resume()
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/users/get_space_usage")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8), let json = jsonToDictionary(jsonStr) {
let totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
let usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
completionHandler(totalSize, usedSize)
return
}
completionHandler(-1, 0)
})
task.resume()
}
open weak var fileOperationDelegate: FileOperationDelegate?
}
extension DropboxFileProvider: FileProviderOperations {
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
doOperation(.create(path: path), completionHandler: completionHandler)
}
public func create(file fileAttribs: FileObject, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
self.writeContents(path: path, contents: data ?? Data(), completionHandler: completionHandler)
}
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
}
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
}
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
doOperation(.remove(path: path), completionHandler: completionHandler)
}
fileprivate func doOperation(_ operation: FileOperation, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return
}
let url: String
var path: String?, fromPath: String?, toPath: String?
switch operation {
case .create(path: let p):
url = "https://api.dropboxapi.com/2/files/create_folder"
path = p
case .copy(source: let fp, destination: let tp):
url = "https://api.dropboxapi.com/2/files/copy"
fromPath = fp
toPath = tp
case .move(source: let fp, destination: let tp):
url = "https://api.dropboxapi.com/2/files/move"
fromPath = fp
toPath = tp
case .modify(path: let p):
return
case .remove(path: let p):
url = "https://api.dropboxapi.com/2/files/delete"
path = p
case .link(link: _, target: _):
return
}
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var requestDictionary = [String: AnyObject]()
requestDictionary["path"] = correctPath(path) as NSString?
requestDictionary["from_path"] = correctPath(fromPath) as NSString?
requestDictionary["to_path"] = correctPath(toPath) as NSString?
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "", errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8)) : nil
defer {
self.delegateNotify(operation, error: error ?? dbError)
}
/*if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
}*/
completionHandler?(dbError)
return
}
completionHandler?(error)
})
task.resume()
}
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .copy(source: localFile.absoluteString, destination: toPath)) ?? true == true else {
return
}
guard let data = try? Data(contentsOf: localFile) else {
let error = throwError(localFile.absoluteString, code: URLError.fileDoesNotExist as FoundationErrorEnum)
completionHandler?(error)
return
}
upload_simple(toPath, data: data, overwrite: true, operation: .copy(source: localFile.absoluteString, destination: toPath), completionHandler: completionHandler)
}
public func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .copy(source: path, destination: destURL.absoluteString)) ?? true == true else {
return
}
let url = URL(string: "https://content.dropboxapi.com/2/files/download")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestDictionary = ["path": path as NSString]
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.downloadTask(with: request, completionHandler: { (cacheURL, response, error) in
guard let cacheURL = cacheURL, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? HTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: nil) : nil
completionHandler?(dbError ?? error)
return
}
do {
try FileManager.default.moveItem(at: cacheURL, to: destURL)
completionHandler?(nil)
} catch let e {
completionHandler?(e)
}
})
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": path as NSString, "dest": destURL.absoluteString as NSString])
task.resume()
}
}
extension DropboxFileProvider: FileProviderReadWrite {
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
}
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
let url = URL(string: "https://content.dropboxapi.com/2/files/download")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
let requestDictionary = ["path": path]
request.setValue(dictionaryToJSON(requestDictionary as [String : AnyObject]), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.dataTask(with: request, completionHandler: { (datam, response, error) in
guard let data = datam, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? HTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: datam ?? Data(), encoding: String.Encoding.utf8)) : nil
completionHandler(nil, dbError ?? error)
return
}
completionHandler(data, error)
})
task.resume()
}
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .modify(path: path)) ?? true == true else {
return
}
// FIXME: remove 150MB restriction
upload_simple(path, data: data, overwrite: true, operation: .modify(path: path), completionHandler: completionHandler)
}
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
var foundFiles = [DropboxFileObject]()
search(path, query: query, foundItem: { (file) in
foundFiles.append(file)
foundItemHandler?(file)
}, completionHandler: { (error) in
completionHandler(foundFiles, error)
})
}
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* 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!
* You can implemnt your own webhook service and replace this method accordingly.
*/
NotImplemented()
}
fileprivate func unregisterNotifcation(path: String) {
NotImplemented()
}
// TODO: Implement /copy_reference, /get_temporary_link, /save_url, /get_account & /get_current_account
}
extension DropboxFileProvider: ExtendedFileProvider {
public func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercased() {
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
}
}
public func propertiesOfFileSupported(path: String) -> Bool {
return false
}
public func thumbnailOfFile(path: String, dimension: CGSize, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
url = URL(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
}
var request = URLRequest(url: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
var requestDictionary = ["path": path as NSString]
requestDictionary["format"] = "jpeg" as NSString
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))" as NSString
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let r = response as? HTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = jsonToDictionary(result) {
if jsonResult["error"] != nil {
completionHandler(nil, self.throwError(path, code: URLError.cannotDecodeRawData as FoundationErrorEnum))
}
}
if let data = data {
image = ImageClass(data: data)
}
completionHandler(image, error)
})
}
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
NotImplemented()
}
}
extension DropboxFileProvider: FileProvider {}
+197
View File
@@ -0,0 +1,197 @@
//
// DropboxHelper.swift
// FileProvider
//
// Created by Amir Abbas Mousavian on 5/18/95.
//
//
import Foundation
public struct FileProviderDropboxError: Error, 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: Date?
public let id: String?
public let rev: String?
// codebeat:disable[ARITY]
public init(name: String, path: String, size: Int64 = -1, serverTime: Date? = nil, modifiedDate: Date? = 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: URL(string: path), name: name, path: path, size: size, createdDate: nil, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
// codebeat:enable[ARITY]
}
// codebeat:disable[ARITY]
internal extension DropboxFileProvider {
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
var requestDictionary = [String: AnyObject]()
let url: URL
if let cursor = cursor {
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
requestDictionary["cursor"] = cursor as NSString?
} else {
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder")!
requestDictionary["path"] = correctPath(path) as NSString?
requestDictionary["recursive"] = recursive as NSNumber?
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8))
}
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8) {
let json = jsonToDictionary(jsonStr)
if let entries = json?["entries"] as? [AnyObject] , 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(files, ncursor, responseError ?? error)
}
return
}
}
completionHandler([], nil, responseError ?? error)
})
task.resume()
}
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperation, completionHandler: SimpleCompletionHandler) {
assert(data.count < 150*1024*1024, "Maximum size of allowed size to upload is 150MB")
var requestDictionary = [String: AnyObject]()
let url: URL
url = URL(string: "https://content.dropboxapi.com/2/files/upload")!
requestDictionary["path"] = correctPath(targetPath) as NSString?
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
requestDictionary["client_modified"] = dateFormatter.string(from: modifiedDate) as NSString
var request = URLRequest(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.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8))
}
defer {
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
}
completionHandler?(responseError ?? error)
})
var dic: [String: AnyObject] = ["type": operation.description as NSString]
switch operation {
case .create(path: let s):
dic["source"] = s as NSString
case .copy(source: let s, destination: let d):
dic["source"] = s as NSString
dic["dest"] = d as NSString
case .modify(path: let s):
dic["source"] = s as NSString
case .move(source: let s, destination: let d):
dic["source"] = s as NSString
dic["dest"] = d as NSString
default:
break
}
task.taskDescription = dictionaryToJSON(dic)
task.resume()
}
func search(_ startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:@escaping ((_ file: DropboxFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/search")!
var request = URLRequest(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 as NSString]
requestDictionary["query"] = query as NSString
requestDictionary["start"] = start as NSNumber
requestDictionary["max_results"] = maxResultPerPage as NSNumber
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: startPath, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8))
}
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8) {
let json = jsonToDictionary(jsonStr)
if let entries = json?["matches"] as? [AnyObject] , entries.count > 0 {
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
foundItem(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(responseError ?? error)
}
return
}
}
completionHandler(responseError ?? error)
})
task.resume()
}
}
// codebeat:enable[ARITY]
internal extension DropboxFileProvider {
func mapToFileObject(_ jsonStr: String) -> DropboxFileObject? {
guard let json = 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)?.int64Value ?? -1
let serverTime = resolve(dateString: json["server_modified"] as? String ?? "")
let modifiedDate = resolve(dateString: 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: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
self.delegate?.fileproviderFailed(self, operation: operation)
}
})
}
}
+523
View File
@@ -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
open class FPSStreamTask: URLSessionTask, StreamDelegate {
fileprivate var inputStream: InputStream?
fileprivate var outputStream: OutputStream?
fileprivate var dispatch_queue: DispatchQueue!
internal var _underlyingSession: URLSession
fileprivate var streamDelegate: FPSStreamDelegate? {
return (_underlyingSession.delegate as? FPSStreamDelegate)
}
fileprivate var _taskIdentifier: Int
@available(iOS 9.0, OSX 10.11, *)
static var streamTasks = [Int: URLSessionStreamTask]()
@available(iOS 9.0, OSX 10.11, *)
internal var _underlyingTask: URLSessionStreamTask? {
return FPSStreamTask.streamTasks[_taskIdentifier]
}
open override var taskIdentifier: Int {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.taskIdentifier
} else {
return _taskIdentifier
}
}
fileprivate var _state: URLSessionTask.State = .suspended
override open var state: URLSessionTask.State {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.state
} else {
return _state
}
}
override open var originalRequest: URLRequest? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.originalRequest
} else {
return nil
}
}
override open var currentRequest: URLRequest? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.currentRequest
} else {
return nil
}
}
fileprivate var _countOfBytesSent: Int64 = 0
fileprivate var _countOfBytesRecieved: Int64 = 0
override open var countOfBytesSent: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesSent
} else {
return _countOfBytesSent
}
}
override open var countOfBytesReceived: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesReceived
} else {
return _countOfBytesRecieved
}
}
override open var countOfBytesExpectedToSend: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesExpectedToSend
} else {
return Int64(dataToBeSent.length)
}
}
override open 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: NetService?
internal init(session: URLSession, host: String, port: Int) {
self._underlyingSession = session
if #available(iOS 9.0, OSX 10.11, *) {
let task = session.streamTask(withHostName: host, port: port)
self._taskIdentifier = task.taskIdentifier
FPSStreamTask.streamTasks[_taskIdentifier] = task
} else {
lasttaskIdAssociated += 1
self._taskIdentifier = lasttaskIdAssociated
self.host = (host, port)
self.dispatch_queue = DispatchQueue(label: "FSPStreamTask", attributes: DispatchQueue.Attributes.concurrent)
}
}
internal init(session: URLSession, netService: NetService) {
self._underlyingSession = session
if #available(iOS 9.0, OSX 10.11, *) {
let task = session.streamTask(with: netService)
self._taskIdentifier = task.taskIdentifier
FPSStreamTask.streamTasks[_taskIdentifier] = task
} else {
lasttaskIdAssociated += 1
self._taskIdentifier = lasttaskIdAssociated
self.service = netService
self.dispatch_queue = DispatchQueue(label: "FSPStreamTask", attributes: DispatchQueue.Attributes.concurrent)
}
}
override open 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?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
self.outputStream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
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: Error? = nil
override open var error: Error? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.error
} else {
return _error
}
}
override open func suspend() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.suspend()
} else {
self._state = .suspended
}
}
override open 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 as CFString, service.type as CFString, service.name as CFString, Int32(service.port))
CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream)
}
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
}
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
inputStream.delegate = self
outputStream.delegate = self
dispatch_queue.sync(execute: {
inputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
outputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
})
inputStream.open()
outputStream.open()
_state = .running
}
}
fileprivate let dataToBeSent: NSMutableData = NSMutableData()
fileprivate 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.
*/
open func readData(OfMinLength minBytes: Int, maxLength maxBytes: Int, timeout: TimeInterval, completionHandler: @escaping (Data?, Bool, NSError?) -> Void) {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.readData(ofMinLength: minBytes, maxLength: maxBytes, timeout: timeout, completionHandler: completionHandler as! (Data?, Bool, Error?) -> Void)
} else {
guard let inputStream = inputStream else {
return
}
var timedOut: Bool = false
dispatch_queue.async {
if timeout > 0 {
self.dispatch_queue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(timeout * 1_000_000_000)) / Double(NSEC_PER_SEC), execute: {
timedOut = true
completionHandler(nil, inputStream.streamStatus == .atEnd, inputStream.streamError as NSError?)
})
}
while (self.dataReceived.length == 0 || self.dataReceived.length < minBytes) && !timedOut {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
let dR = NSMutableData()
if self.dataReceived.length > maxBytes {
let range = NSRange(location: 0, length: maxBytes - 1)
dR.append(self.dataReceived.subdata(with: range))
self.dataReceived.replaceBytes(in: range, withBytes: nil, length: 0)
} else {
dR.append(self.dataReceived as Data)
self.dataReceived.length = 0
}
completionHandler(dR as Data, inputStream.streamStatus == .atEnd, inputStream.streamError as NSError?)
}
}
}
/* 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. */
open func writeData(_ data: Data, timeout: TimeInterval, completionHandler: @escaping (Error?) -> Void) {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.write(data, timeout: timeout, completionHandler: completionHandler)
} else {
guard let outputStream = outputStream else {
return
}
var timedOut: Bool = false
dispatch_queue.async {
if timeout > 0 {
self.dispatch_queue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(timeout * 1_000_000_000)) / Double(NSEC_PER_SEC), execute: {
timedOut = true
completionHandler(self._error)
})
}
self.dataToBeSent.append(data)
while !outputStream.hasSpaceAvailable && !timedOut {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
if self.dataToBeSent.length > 0 {
let bytesWritten = outputStream.write(self.dataToBeSent.bytes.bindMemory(to: UInt8.self, capacity: self.dataToBeSent.length), maxLength: self.dataToBeSent.length)
if bytesWritten > 0 {
let range = NSRange(location: 0, length: bytesWritten)
self.dataToBeSent.replaceBytes(in: 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. */
open func captureStreams() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.captureStreams()
} else {
guard let outputStream = outputStream, let inputStream = inputStream else {
return
}
dispatch_queue.async {
self.write(false)
while inputStream.streamStatus != .atEnd {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
self.streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: 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.
*/
open func closeWrite() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.closeWrite()
} else {
dispatch_queue.async(execute: {
self.write(true)
})
}
}
fileprivate func write(_ close: Bool) {
guard let outputStream = outputStream else {
return
}
while self.dataToBeSent.length > 0 {
let bytesWritten = outputStream.write(self.dataToBeSent.bytes.bindMemory(to: UInt8.self, capacity: self.dataToBeSent.length), maxLength: self.dataToBeSent.length)
if bytesWritten > 0 {
let range = NSRange(location: 0, length: bytesWritten)
self.dataToBeSent.replaceBytes(in: range, withBytes: nil, length: 0)
self._countOfBytesSent += bytesWritten
} else {
self._error = outputStream.streamError as NSError?
}
if self.dataToBeSent.length == 0 {
break
}
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
if close {
outputStream.close()
self.streamDelegate?.urlSession?(self._underlyingSession, writeClosedFor: 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.
*/
open func closeRead() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.closeRead()
} else {
guard let inputStream = inputStream else {
return
}
dispatch_queue.async {
while inputStream.streamStatus != .atEnd {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
inputStream.close()
self.streamDelegate?.urlSession?(self._underlyingSession, readClosedFor: 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:
*/
open func startSecureConnection() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.startSecureConnection()
} else {
inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
}
}
/*
* Cleanly close a secure connection after all pending secure IO has
* completed.
*/
open func stopSecureConnection() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.stopSecureConnection()
} else {
inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
}
}
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch (eventCode) {
case Stream.Event.errorOccurred:
self._error = aStream.streamError as NSError?
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)
case Stream.Event.endEncountered:
break
case Stream.Event():
break
case Stream.Event.openCompleted:
break
case Stream.Event.hasBytesAvailable:
var buffer = [UInt8](repeating: 0, count: 2048)
if (aStream == inputStream) {
while (inputStream!.hasBytesAvailable) {
let len = inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
dataReceived.append(&buffer, length: len)
self._countOfBytesRecieved += len
}
}
}
case Stream.Event.hasSpaceAvailable:
break
default:
break
}
}
}
extension URLSession {
/* 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: NetService) -> FPSStreamTask {
return fpstreamTaskWithNetService(service)
}
}
@objc
public protocol FPSStreamDelegate : URLSessionTaskDelegate {
/* 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. */
@objc optional func urlSession(_ session: URLSession, readClosedFor streamTask: FPSStreamTask)
/* Indiciates that the write side of a connection has been closed.
* Any outstanding writes complete, but future writes will immediately
* fail.
*/
@objc optional func urlSession(_ session: URLSession, writeClosedFor 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. */
@objc optional func urlSession(_ session: URLSession, betterRouteDiscoveredFor 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.
*/
@objc optional func urlSession(_ session: URLSession, streamTask: FPSStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream)
}
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]
+35
View File
@@ -0,0 +1,35 @@
//
// FileProvider iOS.h
// FileProvider iOS
//
// Created by Amir Abbas Mousavian on 5/6/95.
//
//
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
#import <UIKit/UIKit.h>
//! Project version number for FileProvider iOS.
FOUNDATION_EXPORT double FileProvider_iOSVersionNumber;
//! Project version string for FileProvider iOS.
FOUNDATION_EXPORT const unsigned char FileProvider_iOSVersionString[];
#elif defined TARGET_OS_TV
#import <UIKit/UIKit.h>
//! Project version number for FileProvider tvOS.
FOUNDATION_EXPORT double FileProvider_tvOSVersionNumber;
//! Project version string for FileProvider tvOS.
FOUNDATION_EXPORT const unsigned char FileProvider_tvOSVersionString[];
#elif defined TARGET_OS_MAC
#import <Cocoa/Cocoa.h>
//! Project version number for FileProvider OSX.
FOUNDATION_EXPORT double FileProvider_OSXVersionNumber;
//! Project version string for FileProvider OSX.
FOUNDATION_EXPORT const unsigned char FileProvider_OSXVersionString[];
#else
// Unsupported platform
#endif
// In this header, you should import all the public headers of your framework using statements like #import <FileProvider_iOS/PublicHeader.h>
+345
View File
@@ -0,0 +1,345 @@
//
// FileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
#if os(iOS) || os(tvOS)
import UIKit
public typealias ImageClass = UIImage
#elseif os(OSX)
import Cocoa
public typealias ImageClass = NSImage
#endif
public enum FileType: String {
case directory
case regular
case symbolicLink
case socket
case characterSpecial
case blockSpecial
case namedPipe
case unknown
public init(urlResourceTypeValue: URLFileResourceType) {
switch urlResourceTypeValue {
case URLFileResourceType.namedPipe: self = .namedPipe
case URLFileResourceType.characterSpecial: self = .characterSpecial
case URLFileResourceType.directory: self = .directory
case URLFileResourceType.blockSpecial: self = .blockSpecial
case URLFileResourceType.regular: self = .regular
case URLFileResourceType.symbolicLink: self = .symbolicLink
case URLFileResourceType.socket: self = .socket
case URLFileResourceType.unknown: self = .unknown
default: self = .unknown
}
}
public init(fileTypeValue: FileAttributeType) {
switch fileTypeValue {
case FileAttributeType.typeCharacterSpecial: self = .characterSpecial
case FileAttributeType.typeDirectory: self = .directory
case FileAttributeType.typeBlockSpecial: self = .blockSpecial
case FileAttributeType.typeRegular: self = .regular
case FileAttributeType.typeSymbolicLink: self = .symbolicLink
case FileAttributeType.typeSocket: self = .socket
case FileAttributeType.typeUnknown: self = .unknown
default: self = .unknown
}
}
}
public protocol FoundationErrorEnum {
init? (rawValue: Int)
var rawValue: Int { get }
}
extension URLError.Code: FoundationErrorEnum {}
extension CocoaError.Code: FoundationErrorEnum {}
open class FileObject {
open let absoluteURL: URL?
open let name: String
open let path: String
open let size: Int64
open let createdDate: Date?
open let modifiedDate: Date?
open let fileType: FileType
open let isHidden: Bool
open let isReadOnly: Bool
public init(absoluteURL: URL? = nil, name: String, path: String, size: Int64 = -1, createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false) {
self.absoluteURL = absoluteURL
self.name = name
self.path = path
self.size = size
self.createdDate = createdDate
self.modifiedDate = modifiedDate
self.fileType = fileType
self.isHidden = isHidden
self.isReadOnly = isReadOnly
}
open var isDirectory: Bool {
return self.fileType == .directory
}
open var isSymLink: Bool {
return self.fileType == .symbolicLink
}
}
public typealias SimpleCompletionHandler = ((_ error: Error?) -> Void)?
public protocol FileProviderBasic: class {
static var type: String { get }
var isPathRelative: Bool { get }
var baseURL: URL? { get }
var currentPath: String { get set }
var dispatch_queue: DispatchQueue { get set }
var delegate: FileProviderDelegate? { get set }
var credential: URLCredential? { get }
/**
*
*/
func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void))
func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void))
func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void))
}
public protocol FileProviderOperations: FileProviderBasic {
var fileOperationDelegate : FileOperationDelegate? { get set }
func create(folder: String, at: String, completionHandler: SimpleCompletionHandler)
func create(file: FileObject, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler)
func moveItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
func copyItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
func removeItem(path: String, completionHandler: SimpleCompletionHandler)
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler)
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler)
}
public protocol FileProviderReadWrite: FileProviderBasic {
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void))
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void))
func writeContents(path: String, contents: Data, atomically: Bool, completionHandler: SimpleCompletionHandler)
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
}
public protocol FileProviderMonitor: FileProviderBasic {
func registerNotifcation(path: String, eventHandler: @escaping (() -> Void))
func unregisterNotifcation(path: String)
func isRegisteredForNotification(path: String) -> Bool
}
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite {
}
extension FileProviderBasic {
public var type: String {
return type(of: self).type
}
public var bareCurrentPath: String {
return currentPath.trimmingCharacters(in: CharacterSet(charactersIn: ". /"))
}
public func absoluteURL(_ path: String? = nil) -> URL {
let rpath: String
if let path = path {
rpath = path
} else {
rpath = self.currentPath
}
if isPathRelative, let baseURL = baseURL {
if rpath.hasPrefix("/") && baseURL.absoluteString.hasSuffix("/") {
var npath = rpath
npath.remove(at: npath.startIndex)
return baseURL.appendingPathComponent(npath)
} else {
return baseURL.appendingPathComponent(rpath)
}
} else {
return URL(fileURLWithPath: rpath).standardizedFileURL
}
}
public func relativePathOf(url: URL) -> String {
guard let baseURL = self.baseURL else { return url.absoluteString }
return url.standardizedFileURL.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/").removingPercentEncoding!
}
internal func correctPath(_ path: String?) -> String? {
guard let path = path else { return nil }
var p = path.hasPrefix("/") ? path : "/" + path
if p.hasSuffix("/") {
p.remove(at: p.characters.index(before: p.endIndex))
}
return p
}
public func fileByUniqueName(_ filePath: String) -> String {
let fileUrl = URL(fileURLWithPath: filePath)
let dirPath = fileUrl.deletingLastPathComponent().path
let fileName = fileUrl.deletingPathExtension().lastPathComponent
let fileExt = fileUrl.pathExtension
var result = fileName
let group = DispatchGroup()
group.enter()
self.contentsOfDirectory(path: dirPath) { (contents, error) in
var bareFileName = fileName
let number = Int(fileName.components(separatedBy: " ").filter {
!$0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty
}.last ?? "noname")
if let _ = number {
result = fileName.components(separatedBy: " ").filter {
!$0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty
}.dropLast().joined(separator: " ")
bareFileName = result
}
var i = number ?? 2
let similiar = contents.map {
$0.absoluteURL?.lastPathComponent ?? $0.name
}.filter {
$0.hasPrefix(result) && (!fileExt.isEmpty && $0.hasSuffix("." + fileExt))
}
while similiar.contains(result + (!fileExt.isEmpty ? "." + fileExt : "")) {
result = "\(bareFileName) \(i)"
i += 1
}
group.leave()
}
_ = group.wait(timeout: DispatchTime.distantFuture)
let finalFile = result + (!fileExt.isEmpty ? "." + fileExt : "")
return (dirPath as NSString).appendingPathComponent(finalFile)
}
internal func throwError(_ path: String, code: FoundationErrorEnum) -> NSError {
let fileURL = self.absoluteURL(path)
let domain: String
switch code {
case is URLError:
domain = NSURLErrorDomain
default:
domain = NSCocoaErrorDomain
}
return NSError(domain: domain, code: code.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString])
}
internal func NotImplemented() {
assert(false, "method not implemented")
}
internal func resolve(dateString: String) -> Date? {
let dateFor: DateFormatter = DateFormatter()
dateFor.locale = Locale(identifier: "en_US")
dateFor.dateFormat = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss zzz"
if let rfc1123 = dateFor.date(from: dateString) {
return rfc1123
}
dateFor.dateFormat = "EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z"
if let rfc850 = dateFor.date(from: dateString) {
return rfc850
}
dateFor.dateFormat = "EEE MMM d HH':'mm':'ss yyyy"
if let asctime = dateFor.date(from: dateString) {
return asctime
}
dateFor.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
if let isotime = dateFor.date(from: dateString) {
return isotime
}
//self.init()
return nil
}
}
public protocol ExtendedFileProvider: FileProvider {
func thumbnailOfFileSupported(path: String) -> Bool
func propertiesOfFileSupported(path: String) -> Bool
func thumbnailOfFile(path: String, dimension: CGSize, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void))
}
public enum FileOperation: CustomStringConvertible {
case create (path: String)
case copy (source: String, destination: String)
case move (source: String, destination: String)
case modify (path: String)
case remove (path: String)
case link (link: String, target: String)
public var description: String {
switch self {
case .create(path: _): return "Create"
case .copy(source: _, destination: _): return "Copy"
case .move(source: _, destination: _): return "Move"
case .modify(path: _): return "Modify"
case .remove(path: _): return "Remove"
case .link(link: _, target: _): return "Link"
}
}
internal var actionDescription: String {
switch self {
case .create(path: _): return "Creating"
case .copy(source: _, destination: _): return "Copying"
case .move(source: _, destination: _): return "Moving"
case .modify(path: _): return "Modifying"
case .remove(path: _): return "Removing"
case .link(link: _, target: _): return "Linking"
}
}
}
public protocol FileProviderDelegate: class {
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperation)
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperation)
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperation, progress: Float)
}
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.
func fileProvider(_ fileProvider: FileProviderOperations, shouldProceedAfterError error: Error, operation: FileOperation) -> Bool
}
// THESE ARE METHODS TO PROVIDE COMPATIBILITY WITH SWIFT 2.3 SIMOULTANIOUSLY!
internal extension URL {
var uw_scheme: String {
return self.scheme ?? ""
}
}
internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? {
guard let data = jsonString.data(using: String.Encoding.utf8) else {
return nil
}
if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] {
return dic
}
return nil
}
internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? {
if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) {
return String(data: data, encoding: String.Encoding.utf8)
}
return nil
}
+496
View File
@@ -0,0 +1,496 @@
//
// LocalFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
public final class LocalFileObject: FileObject {
public let allocatedSize: Int64
// codebeat:disable[ARITY]
public init(absoluteURL: URL, name: String, path: String, size: Int64 = -1, allocatedSize: Int64 = 0, createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false) {
self.allocatedSize = allocatedSize
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
// codebeat:enable[ARITY]
}
open class LocalFileProvider: FileProvider, FileProviderMonitor {
open static let type = "Local"
open var isPathRelative: Bool = true
open var baseURL: URL? = LocalFileProvider.defaultBaseURL()
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open var operation_queue: DispatchQueue
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential? = nil
open let fileManager = FileManager()
open let opFileManager = FileManager()
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
public init () {
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
public init (baseURL: URL) {
self.baseURL = baseURL
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
fileprivate static func defaultBaseURL() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true);
return URL(fileURLWithPath: paths[0])
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
do {
let contents = try self.fileManager.contentsOfDirectory(at: self.absoluteURL(path), includingPropertiesForKeys: [URLResourceKey.nameKey, URLResourceKey.fileSizeKey, URLResourceKey.fileAllocatedSizeKey, URLResourceKey.creationDateKey, URLResourceKey.contentModificationDateKey, URLResourceKey.isHiddenKey, URLResourceKey.volumeIsReadOnlyKey], options: FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants)
let filesAttributes = contents.map({ (fileURL) -> LocalFileObject in
return self.attributesOfItem(url: fileURL)
})
completionHandler(filesAttributes, nil)
} catch let e as NSError {
completionHandler([], e)
}
}
}
internal func attributesOfItem(url fileURL: URL) -> LocalFileObject {
var namev, sizev, allocated, filetypev, creationDatev, modifiedDatev, hiddenv, readonlyv: AnyObject?
_ = try? (fileURL as NSURL).getResourceValue(&namev, forKey: URLResourceKey.nameKey)
_ = try? (fileURL as NSURL).getResourceValue(&sizev, forKey: URLResourceKey.fileSizeKey)
_ = try? (fileURL as NSURL).getResourceValue(&allocated, forKey: URLResourceKey.fileAllocatedSizeKey)
_ = try? (fileURL as NSURL).getResourceValue(&creationDatev, forKey: URLResourceKey.creationDateKey)
_ = try? (fileURL as NSURL).getResourceValue(&modifiedDatev, forKey: URLResourceKey.contentModificationDateKey)
_ = try? (fileURL as NSURL).getResourceValue(&filetypev, forKey: URLResourceKey.fileResourceTypeKey)
_ = try? (fileURL as NSURL).getResourceValue(&hiddenv, forKey: URLResourceKey.isHiddenKey)
_ = try? (fileURL as NSURL).getResourceValue(&readonlyv, forKey: URLResourceKey.volumeIsReadOnlyKey)
let path: String
if isPathRelative {
path = self.relativePathOf(url: fileURL)
} else {
path = fileURL.path
}
let filetype = URLFileResourceType(rawValue: filetypev as? String ?? "")
let fileAttr = LocalFileObject(absoluteURL: fileURL, name: namev as! String, path: path, size: sizev?.int64Value ?? -1, allocatedSize: allocated?.int64Value ?? -1, createdDate: creationDatev as? Date, modifiedDate: modifiedDatev as? Date, fileType: FileType(urlResourceTypeValue: filetype), isHidden: hiddenv?.boolValue ?? false, isReadOnly: readonlyv?.boolValue ?? false)
return fileAttr
}
open func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
let dict = (try? FileManager.default.attributesOfFileSystem(forPath: baseURL?.path ?? "/"))
let totalSize = (dict?[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? -1;
let freeSize = (dict?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0;
completionHandler(totalSize, totalSize - freeSize)
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
dispatch_queue.async {
completionHandler(self.attributesOfItem(url: self.absoluteURL(path)), nil)
}
}
open weak var fileOperationDelegate : FileOperationDelegate?
@objc(createWithFolder:at:completionHandler:) open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
do {
try self.opFileManager.createDirectory(at: self.absoluteURL(atPath).appendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"))
})
} catch let e as NSError {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"))
})
}
}
}
open func create(file fileAttribs: FileObject, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
let fileURL = self.absoluteURL(atPath).appendingPathComponent(fileAttribs.name)
var attributes = [String : Any]()
if let createdDate = fileAttribs.createdDate {
attributes[FileAttributeKey.creationDate.rawValue] = createdDate as NSDate
}
if let modDate = fileAttribs.modifiedDate {
attributes[FileAttributeKey.modificationDate.rawValue] = modDate as NSDate
}
if fileAttribs.isReadOnly {
attributes[FileAttributeKey.posixPermissions.rawValue] = NSNumber(value: 365 as Int16)
}
let success = self.opFileManager.createFile(atPath: fileURL.path, contents: data, attributes: attributes)
if success {
do {
try (fileURL as NSURL).setResourceValue(fileAttribs.isHidden, forKey: URLResourceKey.isHiddenKey)
} catch _ {}
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(fileAttribs.name)))
})
} else {
completionHandler?(self.throwError(atPath, code: URLError.cannotCreateFile as FoundationErrorEnum))
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(fileAttribs.name)))
})
}
}
}
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: progress
operation_queue.async {
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
completionHandler?(self.throwError(toPath, code: URLError.cannotMoveFile as FoundationErrorEnum))
return
}
do {
try self.opFileManager.moveItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .move(source: path, destination: toPath))
})
} catch let e as NSError {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .move(source: path, destination: toPath))
})
}
}
}
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: progress, for files > 100mb, monitor file by another thread, for dirs check copied items count
operation_queue.async {
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
completionHandler?(self.throwError(toPath, code: URLError.cannotWriteToFile as FoundationErrorEnum))
return
}
do {
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .copy(source: path, destination: toPath))
})
} catch let e as NSError {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .copy(source: path, destination: toPath))
})
}
}
}
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
do {
try self.opFileManager.removeItem(at: self.absoluteURL(path))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .remove(path: path))
})
} catch let e as NSError {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .remove(path: path))
})
}
}
}
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
do {
try self.opFileManager.copyItem(at: localFile, to: self.absoluteURL(toPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .copy(source: localFile.absoluteString, destination: toPath))
})
} catch let e as NSError {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .copy(source: localFile.absoluteString, destination: toPath))
})
}
}
}
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
do {
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: toLocalURL)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .copy(source: path, destination: toLocalURL.absoluteString))
})
} catch let e as NSError {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .copy(source: path, destination: toLocalURL.absoluteString))
})
}
}
}
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
dispatch_queue.async {
let data = self.fileManager.contents(atPath: self.absoluteURL(path).path)
completionHandler(data, nil)
}
}
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
// Unfortunatlely there is no method provided in NSFileManager to read a segment of file.
// So we have to fallback to POSIX provided methods
dispatch_queue.async {
let aPath = self.absoluteURL(path).path
guard !self.attributesOfItem(url: self.absoluteURL(path)).isDirectory && self.fileManager.fileExists(atPath: aPath) else {
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
return
}
let fd_from = open(aPath, O_RDONLY)
if fd_from < 0 {
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
return
}
defer { precondition(close(fd_from) >= 0) }
lseek(fd_from, offset, SEEK_SET)
var buf = [UInt8](repeating: 0, count: length)
let nread = read(fd_from, &buf, buf.count)
if nread < 0 {
completionHandler(nil, self.throwError(path, code: URLError.noPermissionsToReadFile as FoundationErrorEnum))
} else if nread == 0 {
completionHandler(nil, nil)
} else {
let data = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&buf), count: nread, deallocator: .free)
completionHandler(data, nil)
}
}
}
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
try? data.write(to: self.absoluteURL(path), options: atomically ? [.atomic] : [])
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .modify(path: path))
})
}
}
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
let iterator = self.fileManager.enumerator(at: self.absoluteURL(path), includingPropertiesForKeys: nil, options: recursive ? FileManager.DirectoryEnumerationOptions() : .skipsSubdirectoryDescendants) { (url, e) -> Bool in
completionHandler([], e)
return true
}
var result = [LocalFileObject]()
while let fileURL = iterator?.nextObject() as? URL {
if fileURL.lastPathComponent.lowercased().contains(query.lowercased()) {
let fileObject = self.attributesOfItem(url: fileURL)
result.append(self.attributesOfItem(url: fileURL))
foundItemHandler?(fileObject)
}
}
completionHandler(result, nil)
}
}
fileprivate var monitors = [LocalFolderMonitor]()
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
self.unregisterNotifcation(path: path)
let absurl = self.absoluteURL(path)
var isdirv: AnyObject?
do {
try (absurl as NSURL).getResourceValue(&isdirv, forKey: URLResourceKey.isDirectoryKey)
} catch _ {
}
if !(isdirv?.boolValue ?? false) {
return
}
let monitor = LocalFolderMonitor(url: absurl) {
eventHandler()
}
monitor.start()
monitors.append(monitor)
}
open func unregisterNotifcation(path: String) {
var removedMonitor: LocalFolderMonitor?
for (i, monitor) in monitors.enumerated() {
if self.relativePathOf(url: monitor.url) == path {
removedMonitor = monitors.remove(at: i)
break
}
}
removedMonitor?.stop()
}
open func isRegisteredForNotification(path: String) -> Bool {
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path)
}
}
public extension LocalFileProvider {
public func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
do {
try self.opFileManager.createSymbolicLink(at: self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .link(link: path, target: destPath))
})
} catch let e as NSError {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .link(link: path, target: destPath))
})
}
}
}
}
internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
weak var provider: LocalFileProvider?
init(provider: LocalFileProvider) {
self.provider = provider
}
func fileManager(_ fileManager: FileManager, shouldCopyItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .copy(source: srcPath, destination: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldMoveItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .move(source: srcPath, destination: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldRemoveItemAt URL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return true
}
let path = provider.relativePathOf(url: URL)
return delegate.fileProvider(provider, shouldDoOperation: .remove(path: path))
}
func fileManager(_ fileManager: FileManager, shouldLinkItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .link(link: srcPath, target: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, copyingItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .copy(source: srcPath, destination: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, movingItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .move(source: srcPath, destination: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, removingItemAt URL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return false
}
let path = provider.relativePathOf(url: URL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .remove(path: path))
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, linkingItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .link(link: srcPath, target: dstPath))
}
}
internal class LocalFolderMonitor {
fileprivate let source: DispatchSourceFileSystemObject
fileprivate let descriptor: CInt
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
fileprivate var state: Bool = false
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
var url: URL
/// Creates a folder monitor object with monitoring enabled.
init(url: URL, handler: @escaping ()->Void) {
self.url = url
descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: qq)
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
// affect user experince much
let main_handler: ()->Void = {
if Date().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
return
}
self.monitoredTime = Date().timeIntervalSinceReferenceDate
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.25, execute: {
handler()
})
}
source.setEventHandler(handler: main_handler)
source.setCancelHandler {
close(self.descriptor)
}
start()
}
/// Starts sending notifications if currently stopped
func start() {
if !state {
state = true
source.resume()
}
}
/// Stops sending notifications if currently enabled
func stop() {
if state {
state = false
source.suspend()
}
}
deinit {
source.cancel()
}
}
+228
View File
@@ -0,0 +1,228 @@
//
// SMBTransmitter.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
// This client implementation is for little-endian platform, namely x86, x64 & arm
// For big-endian platforms like PowerPC, there must be a huge overhaul
protocol SMBProtocolClientDelegate: class {
func receivedSMB2Response(_ header: SMB2.Header, response: SMBResponse)
}
class SMB2ProtocolClient: FPSStreamTask {
var currentMessageID: UInt64 = 0
var sessionId: UInt64 = 0
weak var delegate: SMBProtocolClientDelegate?
func sendNegotiate(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: UInt16(126), messageId: mId, treeId: UInt32(0), sessionId: UInt64(0))
let msg = SMB2.NegotiateRequest()
let data = createSMB2Message(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
func sendSessionSetup(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let credit = UInt16(sessionId > 0 ? 124 : 125)
let smbHeader = SMB2.Header(command: SMB2.Command.SESSION_SETUP, creditRequestResponse: credit, messageId: mId, treeId: UInt32(0), sessionId: sessionId)
let msg = SMB2.SessionSetupRequest(singing: [])
let data = createSMB2Message(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
if self.sessionId == 0 {
self.readData(OfMinLength: 64, maxLength: 65536, timeout: 30, completionHandler: { (data, eof, e2) in
// TODO: set session id
completionHandler?(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 = ""
let cmp = url.pathComponents
if 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(header: smbHeader, message: msg!)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
func sendTreeDisconnect(id 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(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
func sendLogoff(id 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(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
func messageId() -> UInt64 {
defer {
currentMessageID += 1
}
return currentMessageID
}
// MARK: create and analyse messages
func determineSMBVersion(_ data: Data) -> Float {
var smbverChar: Int8 = 0
(data as NSData).getBytes(&smbverChar, length: 1)
let version = 0 - smbverChar
return Float(version)
}
func digestSMBMessage(_ data: Data) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: Data?)]) {
guard data.count > 30 else {
throw URLError(.badServerResponse)
}
var buffer = [UInt8](repeating: 0, count: data.count)
guard determineSMBVersion(data) == 1 else {
throw SMBFileProviderError.incompatibleHeader
}
let headersize = MemoryLayout<SMB1.Header>.size
let header: SMB1.Header = decode(data)
var blocks = [(params: [UInt16], message: Data?)]()
var offset = headersize
while offset < data.count {
let paramWords: [UInt16]
let paramWordsCount = Int(buffer[offset])
guard data.count > (paramWordsCount * 2 + offset) else {
throw SMBFileProviderError.incorrectParamsLength
}
offset += MemoryLayout<UInt8>.size
var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)])
let paramData = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&rawParamWords), count: rawParamWords.count, deallocator: .free)
paramWords = decode(paramData)
offset += paramWordsCount * 2
let messageBytesCount = Int(UInt16(buffer[0]) + UInt16(buffer[1]) << 8)
offset += MemoryLayout<UInt16>.size
guard data.count >= (offset + messageBytesCount) else {
throw SMBFileProviderError.incorrectMessageLength
}
var rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
offset += messageBytesCount
let message = NSData(bytes: &rawMessage, length: rawMessage.count) as Data
blocks.append((params: paramWords, message: message))
}
return (header, blocks)
}
func digestSMB2Message(_ data: Data) throws -> (header: SMB2.Header, message: SMBResponse?)? {
guard data.count > 65 else {
throw URLError(.badServerResponse)
}
guard determineSMBVersion(data) == 2 else {
throw SMBFileProviderError.incompatibleHeader
}
let headersize = MemoryLayout<SMB2.Header>.size
let headerData = data.subdata(in: NSRange(location: 0, length: headersize))
let messageSize = data.count - headersize
let messageData = data.subdata(in: NSRange(location: headersize, length: messageSize))
let header: SMB2.Header = decode(headerData)
switch header.command {
case .NEGOTIATE:
return (header, SMB2.NegotiateResponse(data: messageData))
case .SESSION_SETUP:
return (header, SMB2.SessionSetupResponse(data: messageData))
case .LOGOFF:
return (header, SMB2.LogOff(data: messageData))
case .TREE_CONNECT:
return (header, SMB2.TreeConnectResponse(data: messageData))
case .TREE_DISCONNECT:
return (header, SMB2.TreeDisconnect(data: messageData))
case .CREATE:
return (header, SMB2.CreateResponse(data: messageData))
case .CLOSE:
return (header, SMB2.CloseResponse(data: messageData))
case .FLUSH:
return (header, SMB2.FlushResponse(data: messageData))
case .READ:
return (header, SMB2.ReadRespone(data: messageData))
case .WRITE:
return (header, SMB2.WriteResponse(data: messageData))
case .LOCK:
return (header, SMB2.LockResponse(data: messageData))
case .IOCTL:
return (header, SMB2.IOCtlResponse(data: messageData))
case .CANCEL:
return (header, nil)
case .ECHO:
return (header, SMB2.Echo(data: messageData))
case .QUERY_DIRECTORY:
return (header, SMB2.QueryDirectoryResponse(data: messageData))
case .CHANGE_NOTIFY:
return (header, SMB2.ChangeNotifyResponse(data: messageData))
case .QUERY_INFO:
return (header, SMB2.QueryInfoResponse(data: messageData))
case .SET_INFO:
return (header, SMB2.SetInfoResponse(data: messageData))
case .OPLOCK_BREAK:
return (header, nil) // FIXME:
case .INVALID:
throw SMBFileProviderError.invalidCommand
}
}
func createSMBMessage(header: SMB1.Header, blocks: [(params: Data?, message: Data?)]) -> Data {
var headerv = header
var result = NSData(data: encode(&headerv)) as Data
for block in blocks {
var paramWordsCount = UInt8(block.params?.count ?? 0)
result.append(&paramWordsCount, count: MemoryLayout.size(ofValue: paramWordsCount))
if let params = block.params {
result.append(params)
}
var messageLen = UInt16(block.message?.count ?? 0)
let b = UnsafeBufferPointer(start: &messageLen, count: MemoryLayout.size(ofValue: messageLen))
result.append(b)
if let message = block.message {
result.append(message)
}
}
return result
}
func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> Data {
var headerv = header
var result = NSData(data: encode(&headerv)) as Data
result.append(message.data() as Data)
return result
}
}
+125
View File
@@ -0,0 +1,125 @@
//
// SambaFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
open class SMBFileProvider: FileProvider, FileProviderMonitor {
open static var type: String = "Samba"
open var isPathRelative: Bool = true
open var baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
public typealias FileObjectClass = FileObject
public init? (baseURL: URL, credential: URLCredential, afterInitialized: SimpleCompletionHandler) {
guard baseURL.uw_scheme.lowercased() == "smb" else {
return nil
}
self.baseURL = baseURL
dispatch_queue = DispatchQueue(label: "FileProvider.\(SMBFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.credential = credential
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObjectClass], _ error: Error?) -> Void)) {
NotImplemented()
dispatch_queue.async {
}
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObjectClass?, _ error: Error?) -> Void)) {
NotImplemented()
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
NotImplemented()
}
open weak var fileOperationDelegate: FileOperationDelegate?
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
open func create(file fileAttribs: FileObject, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
NotImplemented()
}
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
NotImplemented()
}
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler:((FileObjectClass) -> Void)?, completionHandler: @escaping ((_ files: [FileObjectClass], _ error: Error?) -> Void)) {
NotImplemented()
}
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
NotImplemented()
}
open func unregisterNotifcation(path: String) {
NotImplemented()
}
open func isRegisteredForNotification(path: String) -> Bool {
return false
}
}
// MARK: basic CIFS interactivity
public enum SMBFileProviderError: Int, Error, CustomStringConvertible {
case badHeader
case incompatibleHeader
case incorrectParamsLength
case incorrectMessageLength
case invalidCommand
public var description: String {
return "SMB message structure is invalid"
}
}
private extension SMBFileProvider {
func getPID() -> UInt32 {
return UInt32(ProcessInfo.processInfo.processIdentifier)
}
}
+217
View File
@@ -0,0 +1,217 @@
//
// CIFSTypes.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/30/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
// SMB/CIFS Types
struct SMB1 {
struct Header { // 32 bytes
// header is always \u{ff}SMB
let protocolID: UInt32
static let protocolConst: UInt32 = 0x424d53ff
fileprivate var _command: UInt8
var command: Command {
get {
return Command(rawValue: _command) ?? .INVALID
}
set {
_command = newValue.rawValue
}
}
// error messages from the server to the client
fileprivate var _status: (UInt8, UInt8, UInt8, UInt8)
var error: (Class: UInt8, code: UInt16) {
get {
return (_status.0, UInt16(_status.2) + (UInt16(_status.3) << 8))
}
set {
_status = (newValue.Class, 0, UInt8(newValue.code & 0xff), UInt8(newValue.code >> 8))
}
}
var ntStatus: UInt32 {
get {
let statusLo = UInt32(_status.0) + UInt32(_status.1) << 8
let statusHi = UInt32(_status.2) + UInt32(_status.3) << 8
return statusHi << 16 + statusLo
}
set {
_status = (UInt8(newValue & 0xff), UInt8(newValue >> 8 & 0xff), UInt8(newValue >> 16 & 0xff), UInt8(newValue >> 24 & 0xff))
}
}
var flags: Flags
var flags2: Flags2
var pidHigh: UInt16
// encryption key used for validating messages over connectionless transports
fileprivate var _securityKey: (UInt16, UInt16)
var securityKey: UInt32 {
get {
return UInt32(_securityKey.1) << 16 + UInt32(_securityKey.0)
}
set {
_securityKey = (UInt16(newValue & 0xffff), UInt16(newValue >> 16))
}
}
/// Connection identifier
var securityCID: UInt16
/// Identifier of the sequence of a message over connectionless transports
var securitySequenceNumber: UInt16
fileprivate var ununsed: UInt16
var treeId: UInt16
var pidLow: UInt16
var userId: UInt16
var multiplexId: UInt16
var pid: UInt32 {
get {
return UInt32(pidLow) + UInt32(pidHigh) << 16
}
set {
pidLow = UInt16(newValue & 0xffff)
pidHigh = UInt16(newValue >> 16)
}
}
init(command: Command, treeId: UInt16, pid: UInt32, userId: UInt16, multiplexId: UInt16, flags: Flags, flags2: Flags2 = [.LONG_NAMES, .ERR_STATUS, .UNICODE], ntStatus: UInt32 = 0, securityKey: UInt32 = 0, securityCID: UInt16 = 0, securitySequenceNumber: UInt16 = 0) {
self.protocolID = Header.protocolConst
self._command = command.rawValue
_status = (UInt8(ntStatus & 0xff), UInt8(ntStatus >> 8 & 0xff), UInt8(ntStatus >> 16 & 0xff), UInt8(ntStatus >> 24 & 0xff))
self.flags = flags
self.flags2 = flags2
self._securityKey = (UInt16(securityKey & 0xffff), UInt16(securityKey >> 16))
self.securityCID = securityCID
self.securitySequenceNumber = securitySequenceNumber
self.ununsed = 0
self.treeId = treeId
self.pidLow = UInt16(pid & 0xffff)
self.pidHigh = UInt16(pid >> 16)
self.userId = userId
self.multiplexId = multiplexId
}
}
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
/** This bit is set (1) in the NEGOTIATE (0x72) Response if the server supports
* LOCK_AND_READ (0x13) and WRITE_AND_UNLOCK (0x14) commands. */
static let LOCK_AND_READ_OK = Flags(rawValue: 0x01)
static let BUF_AVAIL = Flags(rawValue: 0x02)
static let CASE_INSENSITIVE = Flags(rawValue: 0x08)
static let CANONICALIZED_PATHS = Flags(rawValue: 0x10)
static let OPLOCK = Flags(rawValue: 0x20)
static let OPBATCH = Flags(rawValue: 0x40)
/** When on, this message is being sent from the server in response to a client request. */
static let REPLY = Flags(rawValue: 0x80)
}
struct Flags2: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
/** Client: the message MAY contain long file names. */
static let LONG_NAMES = Flags2(rawValue: 0x0001)
/** Client: the client is aware of extended attributes (EAs). */
static let EAS = Flags2(rawValue: 0x0002)
/** Client: the client is requesting signing (if signing is not yet active) or the message
* being sent is signed. This bit is used on the SMB header of an SESSION_SETUP_ANDX */
static let SMB_SECURITY_SIGNATURE = Flags2(rawValue: 0x0004)
/// Reserved but not implemented.
static let IS_LONG_NAME = Flags2(rawValue: 0x0040)
/** client aware of Extended Security negotiation */
static let EXT_SEC = Flags2(rawValue: 0x0800)
/** any pathnames in this SMB SHOULD be resolved in the Distributed File System (DFS) */
static let DFS = Flags2(rawValue: 0x1000)
/**
* This flag is useful only on a read request. If the bit is set, then the client MAY read
* the file if the client does not have read permission but does have execute permission. */
static let PAGING_IO = Flags2(rawValue: 0x2000) // READ_IF_EXECUTE
/**
* Client: the server MUST return errors as 32-bit NTSTATUS codes in the response
* Server: the Status field in the header is formatted as an NTSTATUS cod */
static let ERR_STATUS = Flags2(rawValue: 0x4000)
/**
* Each field that contains a string in this SMB message MUST be encoded
* as an array of 16-bit Unicode characters */
static let UNICODE = Flags2(rawValue: 0x8000)
}
enum Command: UInt8 {
case CREATE_DIRECTORY = 0x00
case DELETE_DIRECTORY = 0x01
case OPEN = 0x02
case CREATE = 0x03
case CLOSE = 0x04
case FLUSH = 0x05
case DELETE = 0x06
case RENAME = 0x07
case QUERY_INFORMATION = 0x08
case SET_INFORMATION = 0x09
case READ = 0x0A
case WRITE = 0x0B
case LOCK_BYTE_RANGE = 0x0C
case UNLOCK_BYTE_RANGE = 0x0D
case CREATE_TEMPORARY = 0x0E
case CREATE_NEW = 0x0F
case CHECK_DIRECTORY = 0x10
case PROCESS_EXIT = 0x11
case SEEK = 0x12
case LOCK_AND_READ = 0x13
case WRITE_AND_UNLOCK = 0x14
case READ_RAW = 0x1A
case READ_MPX = 0x1B
case READ_MPX_SECONDARY = 0x1C
case WRITE_RAW = 0x1D
case WRITE_MPX = 0x1E
case WRITE_COMPLETE = 0x20
case SET_INFORMATION2 = 0x22
case QUERY_INFORMATION2 = 0x23
case LOCKING_ANDX = 0x24
case TRANSACTION = 0x25
case TRANSACTION_SECONDARY = 0x26
case IOCTL = 0x27
case IOCTL_SECONDARY = 0x28
case COPY = 0x29
case MOVE = 0x2A
case ECHO = 0x2B
case WRITE_AND_CLOSE = 0x2C
case OPEN_ANDX = 0x2D
case READ_ANDX = 0x2E
case WRITE_ANDX = 0x2F
case CLOSE_AND_TREE_DISC = 0x31
case TRANSACTION2 = 0x32
case TRANSACTION2_SECONDARY = 0x33
case FIND_CLOSE2 = 0x34
case FIND_NOTIFY_CLOSE = 0x35
case TREE_CONNECT = 0x70
case TREE_DISCONNECT = 0x71
case NEGOTIATE = 0x72
case SESSION_SETUP_ANDX = 0x73
case LOGOFF_ANDX = 0x74
case TREE_CONNECT_ANDX = 0x75
case QUERY_INFORMATION_DISK = 0x80
case SEARCH = 0x81
case FIND = 0x82
case FIND_UNIQUE = 0x83
case NT_TRANSACT = 0xA0
case NT_TRANSACT_SECONDARY = 0xA1
case NT_CREATE_ANDX = 0xA2
case NT_CANCEL = 0xA4
case OPEN_PRINT_FILE = 0xC0
case WRITE_PRINT_FILE = 0xC1
case CLOSE_PRINT_FILE = 0xC2
case GET_PRINT_QUEUE = 0xC3
case READ_BULK = 0xD8
case WRITE_BULK = 0xD9
case WRITE_BULK_DATA = 0xDA
case INVALID = 0xFE
}
}
+50
View File
@@ -0,0 +1,50 @@
//
// SMB2DataTypes.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/30/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
protocol SMBRequest {
func data() -> Data
}
protocol SMBResponse {
init? (data: Data)
}
protocol IOCtlRequestProtocol: SMBRequest {}
protocol IOCtlResponseProtocol: SMBResponse {}
struct SMBTime {
var time: Int64
init(time: Int64) {
self.time = time
}
init(unixTime: UInt) {
self.time = (Int64(unixTime) + 11644473600) * 10000000
}
init(timeIntervalSince1970: TimeInterval) {
self.time = Int64((timeIntervalSince1970 + 11644473600) * 10000000)
}
init(date: Date) {
self.init(timeIntervalSince1970: date.timeIntervalSince1970)
}
var unixTime: UInt {
return UInt(self.time / 10000000 - 11644473600)
}
var date: Date {
return Date(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600)
}
}
+448
View File
@@ -0,0 +1,448 @@
//
// SMB2CreateClose.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/30/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
extension SMB2 {
// MARK: SMB2 Create
struct CreateRequest: SMBRequest {
let header: CreateRequest.Header
let name: String?
let contexts: [CreateContext]
init (header: CreateRequest.Header, name: String? = nil, contexts: [CreateContext] = []) {
self.header = header
self.name = name
self.contexts = contexts
}
func data() -> Data {
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.data(using: String.Encoding.utf16) {
header.nameOffset = UInt16(offset)
header.nameLength = UInt16(nameData.count)
offset += nameData.count
body.append(nameData)
}
if contexts.count > 0 {
// TODO: Context CreateRequest implementation, 8 bit allign offset
header.contextOffset = UInt32(offset)
header.contextLength = 0
//result.appendData(nameData)
}
var result = NSData(data: encode(&header)) as Data
result.append(body as Data)
return result
}
struct Header {
let size: UInt16
fileprivate let securityFlags: UInt8
fileprivate var _requestedOplockLevel: UInt8
var requestedOplockLevel: OplockLevel {
get {
return OplockLevel(rawValue: _requestedOplockLevel)!
}
set {
_requestedOplockLevel = newValue.rawValue
}
}
fileprivate var _impersonationLevel: UInt32
var impersonationLevel: ImpersonationLevel {
get {
return ImpersonationLevel(rawValue: _impersonationLevel)!
}
set {
_impersonationLevel = newValue.rawValue
}
}
fileprivate let flags: UInt64
fileprivate let reserved: UInt64
let access: FileAccessMask
let fileAttributes: FileAttributes
let shareAccess: ShareAccess
fileprivate var _desposition: UInt32
var desposition: CreateDisposition {
get {
return CreateDisposition(rawValue: _desposition)!
}
set {
_desposition = newValue.rawValue
}
}
let options: CreateOptions
var nameOffset: UInt16
var nameLength: UInt16
var contextOffset: UInt32
var contextLength: UInt32
init(requestedOplockLevel: OplockLevel = .NONE, impersonationLevel: ImpersonationLevel = .anonymous, access: FileAccessMask = [.GENERIC_ALL], fileAttributes: FileAttributes = [], shareAccess: ShareAccess = [.READ], desposition: CreateDisposition = .OPEN_IF, options: CreateOptions = []) {
self.size = 57
self.securityFlags = 0
self._requestedOplockLevel = requestedOplockLevel.rawValue
self._impersonationLevel = impersonationLevel.rawValue
self.flags = 0
self.reserved = 0
self.access = access
self.fileAttributes = fileAttributes
self.shareAccess = shareAccess
self._desposition = desposition.rawValue
self.options = options
self.nameOffset = 0
self.nameLength = 0
self.contextOffset = 0
self.contextLength = 0
}
}
struct CreateOptions: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let DIRECTORY_FILE = CreateOptions(rawValue: 0x00000001)
static let WRITE_THROUGH = CreateOptions(rawValue: 0x00000002)
static let SEQUENTIAL_ONLY = CreateOptions(rawValue: 0x00000004)
static let NO_INTERMEDIATE_BUFFERING = CreateOptions(rawValue: 0x00000008)
static let NON_DIRECTORY_FILE = CreateOptions(rawValue: 0x00000040)
static let NO_EA_KNOWLEDGE = CreateOptions(rawValue: 0x00000200)
static let RANDOM_ACCESS = CreateOptions(rawValue: 0x00000800)
static let DELETE_ON_CLOSE = CreateOptions(rawValue: 0x00001000)
static let OPEN_BY_FILE_ID = CreateOptions(rawValue: 0x00002000)
static let OPEN_FOR_BACKUP_INTENT = CreateOptions(rawValue: 0x00004000)
static let NO_COMPRESSION = CreateOptions(rawValue: 0x00008000)
static let OPEN_REPARSE_POINT = CreateOptions(rawValue: 0x00200000)
static let OPEN_NO_RECALL = CreateOptions(rawValue: 0x00400000)
fileprivate static let SYNCHRONOUS_IO_ALERT = CreateOptions(rawValue: 0x00000010)
fileprivate static let SYNCHRONOUS_IO_NONALERT = CreateOptions(rawValue: 0x00000020)
fileprivate static let COMPLETE_IF_OPLOCKED = CreateOptions(rawValue: 0x00000100)
fileprivate static let REMOTE_INSTANCE = CreateOptions(rawValue: 0x00000400)
fileprivate static let OPEN_FOR_FREE_SPACE_QUERY = CreateOptions(rawValue: 0x00800000)
fileprivate static let OPEN_REQUIRING_OPLOCK = CreateOptions(rawValue: 0x00010000)
fileprivate static let DISALLOW_EXCLUSIVE = CreateOptions(rawValue: 0x00020000)
fileprivate static let RESERVE_OPFILTER = CreateOptions(rawValue: 0x00100000)
}
enum CreateDisposition: UInt32 {
/// If the file already exists, supersede it. Otherwise, create the file.
case SUPERSEDE = 0x00000000
/// If the file already exists, return success; otherwise, fail the operation.
case OPEN = 0x00000001
/// If the file already exists, fail the operation; otherwise, create the file.
case CREATE = 0x00000002
/// Open the file if it already exists; otherwise, create the file.
case OPEN_IF = 0x00000003
/// Overwrite the file if it already exists; otherwise, fail the operation.
case OVERWRITE = 0x00000004
/// Overwrite the file if it already exists; otherwise, create the file.
case OVERWRITE_IF = 0x00000005
}
enum ImpersonationLevel: UInt32 {
case anonymous = 0x00000000
case identification = 0x00000001
case impersonation = 0x00000002
case delegate = 0x00000003
}
}
struct CreateResponse: SMBResponse {
struct Header {
let size: UInt16
fileprivate let _oplockLevel: UInt8
var oplockLevel: OplockLevel {
return OplockLevel(rawValue: _oplockLevel)!
}
fileprivate let reserved: UInt32
let creationTime: SMBTime
let lastAccessTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let allocationSize: UInt64
let endOfFile: UInt64
let fileAttributes: FileAttributes
fileprivate let reserved2: UInt32
let fileId: FileId
let contextsOffset: UInt32
let ContextsLength: UInt32
}
let header: CreateResponse.Header
let contexts: [CreateContext]
init? (data: Data) {
guard data.count >= MemoryLayout<CreateResponse.Header>.size else {
return nil
}
self.header = decode(data)
if self.header.contextsOffset > 0 {
var contexts = [CreateContext]()
var contextOffset = Int(self.header.contextsOffset) - MemoryLayout<SMB2.Header>.size
while contextOffset > 0 {
guard contextOffset < data.count else {
self.contexts = contexts
return
}
let contextDataHeader = data.subdata(in: NSRange(location: contextOffset, length: MemoryLayout<CreateContext.Header>.size))
if let lastContextHeader = CreateContext(data: contextDataHeader) {
let lastContextLen = Int(lastContextHeader.header.dataOffset) + Int(lastContextHeader.header.dataLength) - contextOffset
let lastContextData = data.subdata(in: NSRange(location: contextOffset, length: lastContextLen))
if let newContext = CreateContext(data: lastContextData) {
contexts.append(newContext)
}
contextOffset = Int(lastContextHeader.header.next) - MemoryLayout<SMB2.Header>.size
}
}
self.contexts = contexts
} else {
self.contexts = []
}
}
}
struct CreateContext {
struct Header {
var next: UInt32
let nameOffset: UInt16
let nameLength: UInt16
fileprivate let reserved: UInt16
let dataOffset: UInt16
let dataLength: UInt32
}
var header: CreateContext.Header
let buffer: Data
init(name: ContextNames, data: Data) {
let nameData = NSData(data: (name.rawValue).data(using: String.Encoding.utf16)!) as Data
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.count), reserved: 0, dataOffset: UInt16(nameData.count), dataLength: UInt32(data.count))
self.buffer = data
}
init(name: UUID, data: Data) {
var uuid = uuid_t(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
(name as NSUUID).getBytes(&uuid.0)
let nameData = NSMutableData(bytes: &uuid, length: 16)
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.length), reserved: 0, dataOffset: UInt16(nameData.length), dataLength: UInt32(data.count))
self.buffer = data
}
init? (data: Data) {
let headersize = MemoryLayout<Header>.size
guard data.count > headersize else {
return nil
}
self.header = decode(data)
self.buffer = data.subdata(in: NSRange(location: headersize, length: data.count - headersize))
}
func data() -> Data {
var result = NSData(data: encode(header)) as Data
result.append(buffer)
return result
}
enum ContextNames: String {
/// Request Create Context: Extended attributes
case EA_BUFFER = "ExtA"
/// Request Create Context: Security descriptor
case SD_BUFFER = "SecD"
/// Request & Response Create Context: Open to be durable
case DURABLE_HANDLE = "DHnQ"
case DURABLE_HANDLE_RESPONSE_V2 = "DH2Q"
/// Request Create Context: Reconnect to a durable open after being disconnected
case DURABLE_HANDLE_RECONNECT = "DHnC"
/// Request Create Context: Required allocation size of the newly created file
case ALLOCATION_SIZE = "AISi"
/// Request & Response Create Context: Maximal access information
case QUERY_MAXIMAL_ACCESS = "MxAc"
case TIMEWARP_TOKEN = "TWrp"
/// Response Create Context: DiskID of the open file in a volume.
case QUERY_ON_DISK_ID = "QFid"
/// Response Create Context: A lease. This value is only supported for the SMB 2.1 and 3.x dialect family.
case LEASE = "RqLs"
}
}
enum OplockLevel: UInt8 {
case NONE = 0x00
case LEVEL_II = 0x01
case EXCLUSIVE = 0x08
case BATCH = 0x09
case LEASE = 0xFF
}
struct ShareAccess: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let READ = ShareAccess(rawValue: 0x00000001)
static let WRITE = ShareAccess(rawValue: 0x00000002)
static let DELETE = ShareAccess(rawValue: 0x00000004)
}
struct FileAccessMask: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
// File and Printer/Pipe Accesses
static let FILE_READ_DATA = FileAccessMask(rawValue: 0x00000001)
static let FILE_WRITE_DATA = FileAccessMask(rawValue: 0x00000002)
static let FILE_APPEND_DATA = FileAccessMask(rawValue: 0x00000004)
static let FILE_EXECUTE = FileAccessMask(rawValue: 0x00000020)
// Directory
static let FILE_LIST_DIRECTORY = FileAccessMask(rawValue: 0x00000001)
static let FILE_ADD_FILE = FileAccessMask(rawValue: 0x00000002)
static let FILE_ADD_SUBDIRECTORY = FileAccessMask(rawValue: 0x00000004)
static let FILE_TRAVERSE = FileAccessMask(rawValue: 0x00000020)
// Generic
static let FILE_READ_EA = FileAccessMask(rawValue: 0x00000008)
static let FILE_WRITE_EA = FileAccessMask(rawValue: 0x00000010)
static let FILE_DELETE_CHILD = FileAccessMask(rawValue: 0x00000040)
static let FILE_READ_ATTRIBUTES = FileAccessMask(rawValue: 0x00000080)
static let FILE_WRITE_ATTRIBUTES = FileAccessMask(rawValue: 0x00000100)
static let DELETE = FileAccessMask(rawValue: 0x00010000)
static let READ_CONTROL = FileAccessMask(rawValue: 0x00020000)
static let WRITE_DAC = FileAccessMask(rawValue: 0x00040000)
static let WRITE_OWNER = FileAccessMask(rawValue: 0x00080000)
static let SYNCHRONIZE = FileAccessMask(rawValue: 0x00100000)
static let ACCESS_SYSTEM_SECURITY = FileAccessMask(rawValue: 0x01000000)
static let MAXIMUM_ALLOWED = FileAccessMask(rawValue: 0x02000000)
static let GENERIC_ALL = FileAccessMask(rawValue: 0x10000000)
static let GENERIC_EXECUTE = FileAccessMask(rawValue: 0x20000000)
static let GENERIC_WRITE = FileAccessMask(rawValue: 0x40000000)
static let GENERIC_READ = FileAccessMask(rawValue: 0x80000000)
}
struct FileAttributes: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let READONLY = FileAttributes(rawValue: 0x00000001)
static let HIDDEN = FileAttributes(rawValue: 0x00000002)
static let SYSTEM = FileAttributes(rawValue: 0x00000004)
static let DIRECTORY = FileAttributes(rawValue: 0x00000010)
static let ARCHIVE = FileAttributes(rawValue: 0x00000020)
static let NORMAL = FileAttributes(rawValue: 0x00000080)
static let TEMPORARY = FileAttributes(rawValue: 0x00000100)
static let SPARSE_FILE = FileAttributes(rawValue: 0x00000200)
static let REPARSE_POINT = FileAttributes(rawValue: 0x00000400)
static let COMPRESSED = FileAttributes(rawValue: 0x00000800)
static let OFFLINE = FileAttributes(rawValue: 0x00001000)
static let NOT_CONTENT_INDEXED = FileAttributes(rawValue: 0x00002000)
static let ENCRYPTED = FileAttributes(rawValue: 0x00004000)
static let INTEGRITY_STREAM = FileAttributes(rawValue: 0x00008000)
static let NO_SCRUB_DATA = FileAttributes(rawValue: 0x00020000)
}
struct FileId {
let persistent: UInt64
let volatile: UInt64
}
// MARK: SMB2 Close
struct CloseRequest: SMBRequest {
let size: UInt16
let flags: CloseFlags
fileprivate let reserved2: UInt32
let filePersistantId: UInt64
let fileVolatileId: UInt64
init(filePersistantId: UInt64, fileVolatileId: UInt64) {
self.size = 24
self.filePersistantId = filePersistantId
self.fileVolatileId = fileVolatileId
self.flags = []
self.reserved2 = 0
}
func data() -> Data {
return encode(self)
}
}
struct CloseResponse: SMBResponse {
let size: UInt16
let flags: CloseFlags
fileprivate let reserved: UInt32
let creationTime: SMBTime
let lastAccessTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let allocationSize: UInt64
let endOfFile: UInt64
let fileAttributes: FileAttributes
init? (data: Data) {
self = decode(data)
}
}
struct CloseFlags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
static let POSTQUERY_ATTRIB = Flags(rawValue: 0x0001)
}
// MARK: SMB2 Flush
struct FlushRequest: SMBRequest {
let size: UInt16
fileprivate let reserved: UInt16
fileprivate let reserved2: UInt32
let filePersistantId: UInt64
let fileVolatileId: UInt64
init(filePersistantId: UInt64, fileVolatileId: UInt64) {
self.size = 24
self.filePersistantId = filePersistantId
self.fileVolatileId = fileVolatileId
self.reserved = 0
self.reserved2 = 0
}
func data() -> Data {
return encode(self)
}
}
struct FlushResponse: SMBResponse {
let size: UInt16
let reserved: UInt16
init() {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
}
}
+250
View File
@@ -0,0 +1,250 @@
//
// SMB2FileOperation.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/30/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
extension SMB2 {
// MARK: SMB2 Read
struct ReadRequest: SMBRequest {
let size: UInt16
fileprivate let padding: UInt8
let flags: ReadRequest.Flags
let length: UInt32
let offset: UInt64
let fileId: FileId
let minimumLength: UInt32
fileprivate let _channel: UInt32
var channel: Channel {
return Channel(rawValue: _channel) ?? .NONE
}
let remainingBytes: UInt32
fileprivate let channelInfoOffset: UInt16
fileprivate let channelInfoLength: UInt16
fileprivate let channelBuffer: UInt8
init (fileId: FileId, offset: UInt64, length: UInt32, flags: ReadRequest.Flags = [], minimumLength: UInt32 = 0, remainingBytes: UInt32 = 0, channel: Channel = .NONE) {
self.size = 49
self.padding = 0
self.flags = flags
self.length = length
self.offset = offset
self.fileId = fileId
self.minimumLength = minimumLength
self._channel = channel.rawValue
self.remainingBytes = remainingBytes
self.channelInfoOffset = 0
self.channelInfoLength = 0
self.channelBuffer = 0
}
func data() -> Data {
return encode(read)
}
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
static let UNBUFFERED = Flags(rawValue: 0x01)
}
}
struct ReadRespone: SMBResponse {
struct Header {
let size: UInt16
let offset: UInt8
fileprivate let reserved: UInt8
let length: UInt32
let remaining: UInt32
fileprivate let reserved2: UInt32
}
let header: ReadRespone.Header
let buffer: Data
init?(data: Data) {
guard data.count > 16 else {
return nil
}
self.header = decode(data)
let headersize = MemoryLayout<Header>.size
self.buffer = data.subdata(in: NSRange(location: headersize, length: data.count - headersize))
}
}
enum Channel: UInt32 {
case NONE = 0x00000000
case RDMA_V1 = 0x00000001
case RDMA_V1_INVALIDATE = 0x00000002
}
// MARK: SMB2 Write
struct WriteRequest: SMBRequest {
let header: WriteRequest.Header
let channelInfo: ChannelInfo?
let fileData: Data
struct Header {
let size: UInt16
let dataOffset: UInt16
let length: UInt32
let offset: UInt64
let fileId: FileId
fileprivate let _channel: UInt32
var channel: Channel {
return Channel(rawValue: _channel) ?? .NONE
}
let remainingBytes: UInt32
let channelInfoOffset: UInt16
let channelInfoLength: UInt16
let flags: WriteRequest.Flags
}
init(fileId: FileId, offset: UInt64, remainingBytes: UInt32 = 0, data: Data, channel: Channel = .NONE, channelInfo: ChannelInfo? = nil, flags: WriteRequest.Flags = []) {
var channelInfoOffset: UInt16 = 0
var channelInfoLength: UInt16 = 0
if channel != .NONE, let _ = channelInfo {
channelInfoOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<WriteRequest.Header>.size)
channelInfoLength = UInt16(MemoryLayout<SMB2.ChannelInfo>.size)
}
let dataOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<WriteRequest.Header>.size) + channelInfoLength
self.header = WriteRequest.Header(size: UInt16(49), dataOffset: dataOffset, length: UInt32(data.count), offset: offset, fileId: fileId, _channel: channel.rawValue, remainingBytes: remainingBytes, channelInfoOffset: channelInfoOffset, channelInfoLength: channelInfoLength, flags: flags)
self.channelInfo = channelInfo
self.fileData = data
}
func data() -> Data {
var result = NSData(data: encode(self.header)) as Data
if let channelInfo = channelInfo {
result.append(channelInfo.data())
}
result.append(fileData)
return result
}
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let THROUGH = Flags(rawValue: 0x00000001)
static let UNBUFFERED = Flags(rawValue: 0x00000002)
}
}
struct WriteResponse: SMBResponse {
let size: UInt16
fileprivate let reserved: UInt16
let writtenBytes: UInt32
fileprivate let remaining: UInt32
fileprivate let channelInfoOffset: UInt16
fileprivate let channelInfoLength: UInt16
init?(data: Data) {
self = decode(data)
}
}
struct ChannelInfo: SMBRequest {
let offset: UInt64
let token: UInt32
let length: UInt32
func data() -> Data {
return encode(data)
}
}
// MARK: SMB2 Lock
struct LockElement: SMBRequest {
let offset: UInt64
let length: UInt64
let flags: LockElement.Flags
fileprivate let reserved: UInt32
func data() -> Data {
return encode(self)
}
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let SHARED_LOCK = Flags(rawValue: 0x00000001)
static let EXCLUSIVE_LOCK = Flags(rawValue: 0x00000002)
static let UNLOCK = Flags(rawValue: 0x00000004)
static let FAIL_IMMEDIATELY = Flags(rawValue: 0x00000010)
}
}
struct LockRequest: SMBRequest {
let header: LockRequest.Header
let locks: [LockElement]
init(fileId: FileId,locks: [LockElement], lockSequenceNumber : Int8 = 0, lockSequenceIndex: UInt32 = 0) {
self.header = LockRequest.Header(size: 48, lockCount: UInt16(locks.count), lockSequence: UInt32(lockSequenceNumber << 28) + lockSequenceIndex, fileId: fileId)
self.locks = locks
}
func data() -> Data {
var result = NSData(data: encode(header)) as Data
for lock in locks {
result.append(encode(lock))
}
return result
}
struct Header {
let size: UInt16
fileprivate let lockCount: UInt16
let lockSequence: UInt32
let fileId : FileId
}
}
struct LockResponse: SMBResponse {
let size: UInt16
let reserved: UInt16
init() {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
}
// MARK: SMB2 Cancel
struct CancelRequest: SMBRequest {
let size: UInt16
let reserved: UInt16
init() {
self.size = 4
self.reserved = 0
}
func data() -> Data {
return encode(self)
}
}
}
+389
View File
@@ -0,0 +1,389 @@
//
// SMB2IOCtl.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/30/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
extension SMB2 {
// MARK: SMB2 IOCTL
/**
* IOCtl usage is usually limited in SMB to pipe requests and duplicating file inside server
*/
struct IOCtlRequest: SMBRequest {
let header: Header
let requestData: IOCtlRequestProtocol?
init(fileId: FileId ,ctlCode: IOCtlCode, requestData: IOCtlRequestProtocol?, flags: IOCtlRequest.Flags = []) {
let offset = requestData != nil ? UInt32(MemoryLayout<SMB2.Header>.size + MemoryLayout<IOCtlRequest.Header>.size) : 0
self.header = Header(size: 57, reserved: 0, _ctlCode: ctlCode.rawValue, fileId: fileId, inputOffset: offset, inputCount: UInt32((requestData?.data().count ?? 0)), maxInputResponse: 0, outputOffset: offset, outputCount: 0, maxOutputResponse: UInt32(Int32.max), flags: flags, reserved2: 0)
self.requestData = requestData
}
func data() -> Data {
var result = NSData(data: encode(self.header)) as Data
if let reqData = requestData?.data() {
result.append(reqData)
}
return result
}
struct Header {
let size: UInt16
fileprivate let reserved: UInt16
fileprivate let _ctlCode: UInt32
var ctlCode: IOCtlCode {
return IOCtlCode(rawValue: _ctlCode)!
}
let fileId: FileId
let inputOffset: UInt32
let inputCount: UInt32
let maxInputResponse: UInt32
let outputOffset: UInt32
let outputCount: UInt32
let maxOutputResponse: UInt32
let flags: IOCtlRequest.Flags
fileprivate let reserved2: UInt32
}
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let IOCTL = Flags(rawValue: 0x00000000)
static let FSCTL = Flags(rawValue: 0x00000001)
}
}
struct IOCtlResponse: SMBResponse {
let header: Header
let responseData: IOCtlResponseProtocol?
init?(data: Data) {
self.header = decode(data)
let responseRange = NSRange(location: Int(self.header.outputOffset - 64), length: Int(self.header.outputCount))
let response = data.subdata(in: responseRange)
switch self.header.ctlCode {
case .SRV_COPYCHUNK, .SRV_COPYCHUNK_WRITE:
self.responseData = IOCtlResponseData.SrvCopyChunk(data: response)
case .SRV_ENUMERATE_SNAPSHOTS:
self.responseData = IOCtlResponseData.SrvSnapshots(data: response)
case .SRV_REQUEST_RESUME_KEY:
self.responseData = IOCtlResponseData.ResumeKey(data: response)
case .SRV_READ_HASH:
self.responseData = IOCtlResponseData.ReadHash(data: response)
case .QUERY_NETWORK_INTERFACE_INFO:
self.responseData = IOCtlResponseData.NetworkInterfaceInfo(data: response)
case .VALIDATE_NEGOTIATE_INFO:
self.responseData = IOCtlResponseData.ValidateNegotiateInfo(data: response)
default:
self.responseData = nil
}
}
struct Header {
let size: UInt16
fileprivate let reserved: UInt16
fileprivate let _ctlCode: UInt32
var ctlCode: IOCtlCode {
return IOCtlCode(rawValue: _ctlCode)!
}
let fileId: FileId
let inputOffset: UInt32
let inputCount: UInt32
let outputOffset: UInt32
let outputCount: UInt32
fileprivate let flags: UInt32
fileprivate let reserved2: UInt32
}
}
enum IOCtlCode: UInt32 {
case DFS_GET_REFERRALS = 0x00060194
case DFS_GET_REFERRALS_EX = 0x000601B0
case SET_REPARSE_POINT = 0x000900A4
case FILE_LEVEL_TRIM = 0x00098208
case PIPE_PEEK = 0x0011400C
case PIPE_WAIT = 0x00110018
/// PIPE_TRANSCEIVE is valid only on a named pipe with mode set to FILE_PIPE_MESSAGE_MODE.
case PIPE_TRANSCEIVE = 0x0011C017
/// Get ResumeKey used by the client to uniquely identify the source file in an FSCTL_SRV_COPYCHUNK or FSCTL_SRV_COPYCHUNK_WRITE request.
case SRV_REQUEST_RESUME_KEY = 0x00140078
/// Get all the revision time-stamps that are associated with the Tree Connect share in which the open resides
case SRV_ENUMERATE_SNAPSHOTS = 0x00144064
/// Reads a chunk of file for performing server side copy operations.
case SRV_COPYCHUNK = 0x001440F2
/// Retrieve data from the Content Information File associated with a specified file, not valid for the SMB 2.0.2 dialect.
case SRV_READ_HASH = 0x001441BB
/// Writes the chunk of file for performing server side copy operations.
case SRV_COPYCHUNK_WRITE = 0x001480F2
/// Request resiliency for a specified open file, not valid for the SMB 2.0.2 dialect.
case LMR_REQUEST_RESILIENCY = 0x001401D4
/// Get server network interface info e.g. link speed and socket address information
case QUERY_NETWORK_INTERFACE_INFO = 0x001401FC
/// Request validation of a previous SMB 2 NEGOTIATE, valid for SMB 3.0 and SMB 3.0.2 dialects.
case VALIDATE_NEGOTIATE_INFO = 0x00140204
}
struct IOCtlRequestData {
struct CopyChunk: IOCtlRequestProtocol {
let sourceKey: (UInt64, UInt64, UInt64)
let chunkCount: UInt32
let chunks: [Chunk]
func data() -> Data {
var result = NSData(data: encode(sourceKey)) as Data
result.append(encode(chunkCount))
var reserved: UInt32 = 0
result.append(encode(&reserved))
return Data()
}
struct Chunk {
let sourceOffset: UInt64
let targetOffset: UInt64
let length: UInt32
fileprivate let reserved: UInt32
func data() -> Data {
return encode(self)
}
}
}
struct ReadHash: IOCtlRequestProtocol {
let _hashType: UInt32
var hashType: IOCtlHashType {
return IOCtlHashType(rawValue: _hashType) ?? .PEER_DIST
}
let _hashVersion: UInt32
var hashVersion: IOCtlHashVersion {
return IOCtlHashVersion(rawValue: _hashVersion) ?? .VER_1
}
let _hashRetrievalType: UInt32
var hashRetrievalType: IOCtlHashRetrievalType {
return IOCtlHashRetrievalType(rawValue: _hashRetrievalType) ?? .FILE_BASED
}
let length: UInt32
let offset: UInt64
init(offset: UInt64, length: UInt32, hashType: IOCtlHashType = .PEER_DIST, hashVersion: IOCtlHashVersion = .VER_1, hashRetrievalType: IOCtlHashRetrievalType = .FILE_BASED) {
self._hashType = hashType.rawValue
self._hashVersion = hashVersion.rawValue
self._hashRetrievalType = hashRetrievalType.rawValue
self.length = length
self.offset = offset
}
func data() -> Data {
return encode(self)
}
}
struct ResilencyRequest: IOCtlRequestProtocol {
let timeout: UInt32
fileprivate let reserved: UInt32
/// The requested time the server holds the file open after a disconnect before releasing it. This time is in milliseconds.
init(timeout: UInt32) {
self.timeout = timeout
self.reserved = 0
}
func data() -> Data {
return encode(self)
}
}
struct ValidateNegotiateInfo: IOCtlRequestProtocol {
let header: ValidateNegotiateInfo.Header
let dialects: [UInt16]
init(dialects: [UInt16], guid: uuid_t, capabilities: IOCtlCapabilities, securityMode: UInt16) {
self.header = Header(capabilities: capabilities, guid: guid, securityMode: securityMode, dialectCount: UInt16(dialects.count))
self.dialects = dialects
}
func data() -> Data {
var result = NSData(data: encode(self.header)) as Data
dialects.forEach { result.append(encode($0)) }
return result
}
struct Header {
let capabilities: IOCtlCapabilities
/// Client's GUID
let guid: uuid_t
let securityMode: UInt16
let dialectCount: UInt16
}
}
}
struct IOCtlResponseData {
// SRV_COPYCHUNK, SRV_COPYCHUNK_WRITE
struct SrvCopyChunk: IOCtlResponseProtocol {
let chunksCount: UInt32
let chunksBytesWritten: UInt32
let totalBytesWriiten: UInt32
init?(data: Data) {
self = decode(data)
}
}
// SRV_ENUMERATE_SNAPSHOTS
struct SrvSnapshots: IOCtlResponseProtocol {
let count: UInt32
let returnedCount: UInt32
let snapshots: [SMBTime]
init?(data: Data) {
self.count = decode(data)
self.returnedCount = decode(data.subdata(in: NSRange(location: 4, length: 4)))
//let size: UInt32 = decode(data.subdataWithRange(NSRange(location: 8, length: 4)))
var snapshots = [SMBTime]()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "'@GMT-'yyyy'.'MM'.'dd'-'HH'.'mm'.'ss"
for i in 0..<Int(returnedCount) {
let offset = 24 + i * 48
if data.count < offset + 48 {
return nil
}
let datestring = String(data: data.subdata(in: NSRange(location: offset, length: 48)), encoding: String.Encoding.utf16)
if let datestring = datestring, let date = dateFormatter.date(from: datestring) {
snapshots.append(SMBTime(date: date))
}
}
self.snapshots = snapshots
}
}
struct ResumeKey: IOCtlResponseProtocol {
let key: (UInt64, UInt64, UInt64)
fileprivate let contextLength: UInt32
fileprivate let context: UInt32
init?(data: Data) {
self = decode(data)
}
}
struct ReadHash: IOCtlResponseProtocol {
// TODO: Implement IOCTL READ_HASH
init?(data: Data) {
self = decode(data)
}
}
struct NetworkInterfaceInfo: IOCtlResponseProtocol {
let items: [NetworkInterfaceInfo.Item]
init?(data: Data) {
let count = data.count / MemoryLayout<Item>.size
guard count > 0 else {
return nil
}
var items = [Item]()
for i in 0..<count {
let itemdata = data.subdata(in: NSRange(location: i * MemoryLayout<Item>.size, length: MemoryLayout<Item>.size))
items.append(decode(itemdata))
}
self.items = items
}
struct Item {
/// The offset, in bytes, from the beginning of this structure to the beginning of a subsequent 8-byte aligned network interface.
let next: UInt32
/// specifies the network interface index.
let ifIndex: UInt32
let capability: IOCtlCapabilities
fileprivate let reserved: UInt32
/// Speed of the network interface in bits per second
let linkSpeed: UInt64
fileprivate let sockaddrStorage: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
var family: sa_family_t {
return sockaddrStorage.1
}
static let ipv4: sa_family_t = 0x02
static let ipv6: sa_family_t = 0x17
var sockaddr: sockaddr_in {
var sockaddrStorage = self.sockaddrStorage
let data = Data(bytes: &sockaddrStorage, count: 16)
return decode(data)
}
var sockaddr6: sockaddr_in6 {
var sockaddrStorage = self.sockaddrStorage
let data = Data(bytes: &sockaddrStorage, count: 28)
return decode(data)
}
}
}
struct ValidateNegotiateInfo: IOCtlResponseProtocol {
let capabilities: IOCtlCapabilities
let guid: uuid_t
let securityMode: UInt16
fileprivate let _dialect: UInt16
var dialect: (major: Int, minor: Int) {
return (major: Int(_dialect & 0xFF), minor: Int(_dialect >> 8))
}
init?(data: Data) {
self = decode(data)
}
}
}
struct IOCtlCapabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let RSS_CAPABLE = IOCtlCapabilities(rawValue: 0x00000001)
static let RDMA_CAPABLE = IOCtlCapabilities(rawValue: 0x00000002)
}
enum IOCtlHashType: UInt32 {
case PEER_DIST = 0x00000001
}
enum IOCtlHashVersion: UInt32 {
case VER_1 = 0x00000001
case VER_2 = 0x00000002
}
enum IOCtlHashRetrievalType: UInt32 {
case HASH_BASED = 0x00000001
case FILE_BASED = 0x00000002
}
}
+140
View File
@@ -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
fileprivate 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() -> Data {
return encode(self)
}
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
static let WATCH_TREE = Flags(rawValue: 0x0001)
}
struct CompletionFilter: OptionSet {
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: Data) {
let maxLoop = 1000
var i = 0
var result = [(action: FileNotifyAction, fileName: String)]()
var offset: UInt32 = 0
while i < maxLoop {
let actionData = data.subdata(in: NSRange(location: Int(offset + 4), length: 4))
let actionValue: UInt32 = decode(actionData)
guard let action = FileNotifyAction(rawValue: actionValue) else {
continue
}
let fileLenData = data.subdata(in: NSRange(location: Int(offset + 8), length: 4))
let fileNameLen: UInt32 = decode(fileLenData)
let fileNameData = data.subdata(in: NSRange(location: Int(offset + 12), length: Int(12 + fileNameLen)))
let fileName = String(data: fileNameData, encoding: String.Encoding.utf16) ?? ""
result.append((action: action, fileName: fileName))
let nextOffsetData = data.subdata(in: 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
}
}
+339
View File
@@ -0,0 +1,339 @@
//
// SMB2Query.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/31/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
extension SMB2 {
// MARK: SMB2 Query Directory
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 ? MemoryLayout<SMB2.Header>.size + MemoryLayout<QueryDirectoryRequest.Header>.size : 0
let nflags = flags.intersection(fileIndex > 0 ? [.INDEX_SPECIFIED] : [])
let searchPatternLength = searchPattern?.data(using: String.Encoding.utf16)?.count ?? 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() -> Data {
var result = NSData(data: encode(header)) as Data
if let patternData = searchPattern?.data(using: String.Encoding.utf16) {
result.append(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: OptionSet {
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: Data
func parseAs(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.subdata(in: NSRange(location: offset, length: MemoryLayout<FileDirectoryInformationHeader>.size))
let h: FileDirectoryInformationHeader = decode(headerData)
header = h
case .fileFullDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileFullDirectoryInformationHeader>.size))
let h: FileFullDirectoryInformationHeader = decode(headerData)
header = h
case .fileIdFullDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileIdFullDirectoryInformationHeader>.size))
let h: FileIdFullDirectoryInformationHeader = decode(headerData)
header = h
case .fileBothDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileBothDirectoryInformationHeader>.size))
let h: FileBothDirectoryInformationHeader = decode(headerData)
header = h
case .fileIdBothDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileIdBothDirectoryInformationHeader>.size))
let h: FileIdBothDirectoryInformationHeader = decode(headerData)
header = h
case .fileNamesInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileNamesInformationHeader>.size))
let h: FileNamesInformationHeader = decode(headerData)
header = h
default:
return []
}
let fnData = buffer.subdata(in: NSRange(location: offset + MemoryLayout.size(ofValue: header), length: Int(header.fileNameLength)))
let fileName = String(data: fnData, encoding: String.Encoding.utf16) ?? ""
result.append((header: header, fileName: fileName))
if header.nextEntryOffset == 0 {
break
}
offset += Int(header.nextEntryOffset)
}
return result
}
init? (data: Data) {
let offset: UInt16 = decode(data.subdata(in: NSRange(location: 2, length: 2)))
let length: UInt32 = decode(data.subdata(in: NSRange(location: 4, length: 4)))
guard data.count > Int(offset) + Int(length) else {
return nil
}
self.buffer = data.subdata(in: NSRange(location: Int(offset), length: Int(length)))
}
}
// MARK: SMB2 Query Info
struct QueryInfoRequest: SMBRequest {
let header: Header
let buffer: Data?
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.data(using: String.Encoding.ascii)!
let strLength = UInt8(strData.count)
let nextOffset = UInt32(4 + 1 + strData.count)
let data = (encode(nextOffset) as NSData).mutableCopy() as! NSMutableData
data.append(encode(strLength))
data.append(strData)
data.length += 1
let padSize = (data.length) % 4
data.length += padSize
buffer.append(data as Data)
}
let bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<QueryInfoRequest.Header>.size)
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 as Data
}
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() -> Data {
let headerData = encode(header)
var result = NSData(data: headerData) as Data
if let buffer = buffer {
result.append(buffer)
}
return result
}
struct Header {
let size: UInt16
let infoType: UInt8
let infoClass: UInt8
let outputBufferLength: UInt32
let inputBufferOffset: UInt16
fileprivate let reserved: UInt16
let inputBufferLength: UInt32
let additionalInformation: FileSecurityInfo
let flags: QueryInfoRequest.Flags
let fileId: FileId
}
struct Flags: OptionSet {
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: Data
init?(data: Data) {
let structSizeData = data.subdata(in: 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.subdata(in: NSRange(location: 4, length: 4))
let length: UInt32 = decode(lengthData)
guard data.count >= 8 + Int(length) else {
return nil
}
self.buffer = data.subdata(in: 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.subdata(in: NSRange(location: MemoryLayout<FileAllInformationHeader>.size, length: Int(header.nameLength)))
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
return (header, name)
}
var asAlternateNameInformation: String {
let b = (buffer as NSData).bytes.bindMemory(to: CChar.self, capacity: buffer.count)
return String(cString: b, encoding: String.Encoding.utf16) ?? ""
}
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.subdata(in: NSRange(location: MemoryLayout<FileStreamInformationHeader>.size, length: Int(header.streamNameLength)))
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
return (header, name)
}
var asFsVolumeInformation: (header: FileFsVolumeInformationHeader, name: String) {
let header: FileFsVolumeInformationHeader = decode(buffer)
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileFsVolumeInformationHeader>.size, length: Int(header.labelLength)))
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
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.subdata(in: NSRange(location: MemoryLayout<FileFsAttributeInformationHeader>.size, length: Int(header.nameLength)))
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
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)
}
}
}
+623
View File
@@ -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: OptionSet {
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: Data) {
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: Data) {
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
fileprivate let reserved: UInt32
let fileId: FileId
init?(data: Data) {
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
fileprivate let shortNameLen: UInt8
fileprivate let reserved: UInt8
fileprivate let _shortName: FileShortNameType
var shortName: String? {
let s = encode(_shortName)
var d = NSData(data: s) as Data
d.count = Int(shortNameLen)
return String(data: d, encoding: String.Encoding.utf16)
}
init?(data: Data) {
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
fileprivate let shortNameLen: UInt8
fileprivate let reserved: UInt8
fileprivate let _shortName: FileShortNameType
var shortName: String? {
let s = encode(_shortName)
var d = NSData(data: s) as Data
d.count = Int(shortNameLen)
return String(data: d, encoding: String.Encoding.utf16)
}
fileprivate let reserved2: UInt16
let fileId : FileId
init?(data: Data) {
self = decode(data)
}
}
struct FileNamesInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let fileNameLength : UInt32
init?(data: Data) {
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 {
fileprivate 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
fileprivate 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
fileprivate let reserved: (UInt8, UInt16)
}
struct FileEaInformation {
let eaSize: UInt32
}
struct FileFullEaInformation {
// TODO
}
struct FileInternalInformation {
let indexNumber: UInt64
}
struct FileModeInformation {
let mode: Mode
struct Mode: OptionSet {
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
fileprivate let reserved: UInt32
}
struct FilePipeInformation {
fileprivate let _readMode: UInt32
var readMode: ReadMode {
return ReadMode(rawValue: _readMode) ?? .BYTE_STREAM_MODE
}
fileprivate 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 {
fileprivate let _namedPipeType: UInt32
var namedPipeType: Type {
return Type(rawValue: _namedPipeType) ?? .BYTE_STREAM_TYPE
}
fileprivate 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
fileprivate let _namedPipeState: UInt32
var namedPipeState: State {
return State(rawValue: _namedPipeState) ?? .DISCONNECTED_STATE
}
fileprivate 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
fileprivate 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 {
fileprivate 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: OptionSet {
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: OptionSet {
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
fileprivate let padding: UInt32 = 0
struct Flags: OptionSet {
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: OptionSet {
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)
}
}
}
+329
View File
@@ -0,0 +1,329 @@
//
// SMB2NegotiationTypes.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/30/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
extension SMB2 {
// MARK: SMB2 Negotiating
struct NegotiateRequest: SMBRequest {
let header: NegotiateRequest.Header
let dialects: [UInt16]
let contexts: [(type: NegotiateContextType, data: Data)]
init(header: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: Data)] = []) {
self.header = header
self.dialects = dialects
self.contexts = contexts
}
init(dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: Data)] = [],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() -> Data {
var header = self.header
header.dialectCount = UInt16(dialects.count)
let dialectData = NSMutableData()
for dialect in dialects {
var dialect = dialect
dialectData.append(&dialect, length: 2)
}
let pad = ((1024 - dialectData.length) % 8)
dialectData.increaseLength(by: pad)
header.contextOffset = UInt32(MemoryLayout<NegotiateRequest.Header>.size) + UInt32(dialectData.length)
header.contextCount = UInt16(contexts.count)
let contextData = NSMutableData()
for context in contexts {
var contextType = context.type.rawValue
contextData.append(&contextType, length: 2)
var dataLen = UInt16(context.data.count)
contextData.increaseLength(by: 4)
contextData.append(&dataLen, length: 2)
}
var result = NSData(data: encode(&header)) as Data
result.append(dialectData as Data)
result.append(contextData as Data)
return result
}
struct Header {
var size: UInt16
var dialectCount: UInt16
let signing: NegotiateSinging
fileprivate let reserved: UInt16
let capabilities: GlobalCapabilities
let guid: uuid_t
var contextOffset: UInt32
var contextCount: UInt16
fileprivate let reserved2: UInt16
var clientStartTime: SMBTime {
let time = Int64(contextOffset) + (Int64(contextCount) << 32) + (Int64(contextCount) << 48)
return SMBTime(time: time)
}
init(capabilities: GlobalCapabilities, clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) {
self.size = 36
self.dialectCount = 0
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)
if let clientStartTime = clientStartTime {
let time = clientStartTime.time
self.contextOffset = UInt32(time & 0xffffffff)
self.contextCount = UInt16(time & 0x0000ffff00000000 >> 32)
self.reserved2 = UInt16(time >> 48)
} else {
self.contextOffset = 0
self.contextCount = 0
self.reserved2 = 0
}
}
}
}
struct NegotiateResponse: SMBResponse {
let header: NegotiateResponse.Header
let buffer: Data?
let contexts: [(type: NegotiateContextType, data: Data)]
init? (data: Data) {
if data.count < 64 {
return nil
}
self.header = decode(data)
if Int(header.size) != 65 {
return nil
}
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
let bufLen = Int(self.header.bufferLength)
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
self.buffer = data.subdata(in: NSRange(location: bufOffset, length: bufLen))
} else {
self.buffer = nil
}
let contextCount = Int(self.header.contextCount)
let contextOffset = Int(self.header.contextOffset) - MemoryLayout<SMB2.Header>.size
if contextCount > 0 && contextOffset > 0 {
// TODO: NegotiateResponse context support for SMB3
self.contexts = []
} else {
self.contexts = []
}
}
struct Header {
let size: UInt16
let singing: NegotiateSinging
let dialect: UInt16
let contextCount: UInt16
let serverGuid: uuid_t
let capabilities: GlobalCapabilities
let maxTransactSize: UInt32
let maxReadSize: UInt32
let maxWriteSize: UInt32
let systemTime: SMBTime
let serverStartTime: SMBTime
let bufferOffset: UInt16
let bufferLength: UInt16
let contextOffset: UInt32
}
}
struct NegotiateSinging: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
static let ENABLED = NegotiateSinging(rawValue: 0x0001)
static let REQUIRED = NegotiateSinging(rawValue: 0x0002)
}
struct NegotiateContextType: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
static let PREAUTH_INTEGRITY_CAPABILITIES = NegotiateContextType(rawValue: 0x0001)
static let ENCRYPTION_CAPABILITIES = NegotiateContextType(rawValue: 0x0002)
}
struct GlobalCapabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let DFS = GlobalCapabilities(rawValue: 0x00000001)
static let LEASING = GlobalCapabilities(rawValue: 0x00000002)
static let LARGE_MTU = GlobalCapabilities(rawValue: 0x00000004)
static let MULTI_CHANNEL = GlobalCapabilities(rawValue: 0x00000008)
static let PERSISTENT_HANDLES = GlobalCapabilities(rawValue: 0x00000010)
static let DIRECTORY_LEASING = GlobalCapabilities(rawValue: 0x00000020)
static let ENCRYPTION = GlobalCapabilities(rawValue: 0x00000040)
}
// MARK: SMB2 Session Setup
struct SessionSetupRequest: SMBRequest {
let header: SessionSetupRequest.Header
let buffer: Data?
init(header: SessionSetupRequest.Header, buffer: Data) {
self.header = header
self.buffer = buffer
}
init(sessionId: UInt64 = 0, flags: SessionSetupRequest.Flags = [], singing: SessionSetupSinging = [.ENABLED], capabilities: GlobalCapabilities = [], securityData: Data? = nil) {
self.header = Header(sessionId: sessionId, flags: flags, singing: singing, capabilities: capabilities)
self.buffer = securityData
}
func data() -> Data {
var header = self.header
header.bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<SessionSetupRequest.Header>.size)
header.bufferLength = UInt16(buffer?.count ?? 0)
var result = NSData(data: encode(&header)) as Data
if let buffer = self.buffer {
result.append(buffer)
}
return result
}
struct Header {
let size: UInt16
let flags: SessionSetupRequest.Flags
let signing: SessionSetupSinging
let capabilities: GlobalCapabilities
fileprivate let channel: UInt32
var bufferOffset: UInt16
var bufferLength: UInt16
let sessionId: UInt64
init(sessionId: UInt64, flags: SessionSetupRequest.Flags = [], singing: SessionSetupSinging, capabilities: GlobalCapabilities) {
self.size = 25
self.flags = flags
self.signing = singing
self.capabilities = capabilities
self.channel = 0
self.bufferOffset = 0
self.bufferLength = 0
self.sessionId = sessionId
}
}
/// Works the client implements the SMB 3.x dialect family
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
static let BINDING = NegotiateSinging(rawValue: 0x01)
}
}
struct SessionSetupResponse: SMBResponse {
let header: SessionSetupResponse.Header
let buffer: Data?
init? (data: Data) {
if data.count < 64 {
return nil
}
self.header = decode(data)
if Int(header.size) != 9 {
return nil
}
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
let bufLen = Int(self.header.bufferLength)
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
self.buffer = data.subdata(in: NSRange(location: bufOffset, length: bufLen))
} else {
self.buffer = nil
}
}
struct Header {
let size: UInt16
let flags: SessionSetupResponse.Flags
let bufferOffset: UInt16
let bufferLength: UInt16
}
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
static let IS_GUEST = Flags(rawValue: 0x0001)
static let IS_NULL = Flags(rawValue: 0x0002)
static let ENCRYPT_DATA = Flags(rawValue: 0x0004)
}
}
struct SessionSetupSinging: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
static let ENABLED = SessionSetupSinging(rawValue: 0x01)
static let REQUIRED = SessionSetupSinging(rawValue: 0x02)
}
// MARK: SMB2 Log off
struct LogOff: SMBRequest, SMBResponse {
let size: UInt16
let reserved: UInt16
init() {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
func data() -> Data {
return encode(self)
}
}
// MARK: SMB2 Echo
struct Echo: SMBRequest, SMBResponse {
let size: UInt16
let reserved: UInt16
init() {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
func data() -> Data {
return encode(self)
}
}
}
+46
View File
@@ -0,0 +1,46 @@
//
// SMB2SetInfo.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/31/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
extension SMB2 {
// MARK: SMB2 Set Info
struct SetInfoRequest: SMBRequest {
let header: Header
let buffer: Data?
func data() -> Data {
return Data()
}
struct Header {
let size: UInt16 = 33
let infoType: UInt8
fileprivate let infoClass: UInt8
let bufferLength: UInt32
let bufferOffset: UInt16
fileprivate let reserved: UInt16
let securityInfo: FileSecurityInfo
let fileId: FileId
}
}
struct SetInfoResponse: SMBResponse {
let size: UInt16
init() {
self.size = 2
}
init? (data: Data) {
self = decode(data)
}
}
}
+151
View File
@@ -0,0 +1,151 @@
//
// SMB2Tree.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/30/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
extension SMB2 {
// MARK: SMB2 Tree Connect
struct TreeConnectRequest: SMBRequest {
let header: TreeConnectRequest.Header
let buffer: Data?
var path: String {
return ""
}
var share: String {
return ""
}
init? (header: TreeConnectRequest.Header, host: String, share: String) {
guard !host.contains("/") && !share.contains("/") else {
return nil
}
self.header = header
let path = "\\\\\(host)\\\(share)"
self.buffer = path.data(using: String.Encoding.utf16)
}
func data() -> Data {
var header = self.header
header.pathOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<TreeConnectRequest.Header>.size)
header.pathLength = UInt16(buffer?.count ?? 0)
var result = NSData(data: encode(&header)) as Data
if let buffer = self.buffer {
result.append(buffer)
}
return result
}
struct Header {
let size: UInt16
let flags: TreeConnectRequest.Flags
var pathOffset: UInt16
var pathLength: UInt16
init(flags: TreeConnectRequest.Flags) {
self.size = 9
self.flags = flags
self.pathOffset = 0
self.pathLength = 0
}
}
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
static let SHAREFLAG_CLUSTER_RECONNECT = Flags(rawValue: 0x0001)
}
}
struct TreeConnectResponse: SMBResponse {
let size: UInt16 // = 16
fileprivate let _type: UInt8
var type: ShareType {
return ShareType(rawValue: _type) ?? .UNKNOWN
}
fileprivate let reserved: UInt8
let flags: TreeConnectResponse.ShareFlags
let capabilities: TreeConnectResponse.Capabilities
let maximalAccess: FileAccessMask
init? (data: Data) {
if data.count != 16 {
return nil
}
self = decode(data)
}
enum ShareType: UInt8 {
case UNKNOWN = 0x00
case DISK = 0x01
case PIPE = 0x02
case PRINT = 0x03
}
struct ShareFlags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let DFS = ShareFlags(rawValue: 0x00000001)
static let DFS_ROOT = ShareFlags(rawValue: 0x00000002)
static let MANUAL_CACHING = ShareFlags(rawValue: 0x00000000)
static let AUTO_CACHING = ShareFlags(rawValue: 0x00000010)
static let VDO_CACHING = ShareFlags(rawValue: 0x00000020)
static let NO_CACHING = ShareFlags(rawValue: 0x00000030)
static let RESTRICT_EXCLUSIVE_OPENS = ShareFlags(rawValue: 0x00000100)
static let FORCE_SHARED_DELETE = ShareFlags(rawValue: 0x00000200)
static let ALLOW_NAMESPACE_CACHING = ShareFlags(rawValue: 0x00000400)
static let ACCESS_BASED_DIRECTORY_ENUM = ShareFlags(rawValue: 0x00000800)
static let FORCE_LEVELII_OPLOCK = ShareFlags(rawValue: 0x00001000)
static let ENABLE_HASH_V1 = ShareFlags(rawValue: 0x00002000)
static let ENABLE_HASH_V2 = ShareFlags(rawValue: 0x00004000)
static let ENCRYPT_DATA = ShareFlags(rawValue: 0x00008000)
}
struct Capabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let DFS = Capabilities(rawValue: 0x00000008)
static let CONTINUOUS_AVAILABILITY = Capabilities(rawValue: 0x00000010)
static let SCALEOUT = Capabilities(rawValue: 0x00000020)
static let CLUSTER = Capabilities(rawValue: 0x00000040)
static let ASYMMETRIC = Capabilities(rawValue: 0x00000080)
}
}
// MARK: SMB2 Tree Disconnect
struct TreeDisconnect: SMBRequest, SMBResponse {
let size: UInt16
let reserved: UInt16
init() {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
func data() -> Data {
return encode(self)
}
}
}
+164
View File
@@ -0,0 +1,164 @@
//
// SMB2Types.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
internal func encode<T>(_ value: inout T) -> Data {
return withUnsafePointer(to: &value) { p in
NSData(bytes: p, length: MemoryLayout.size(ofValue: value)) as Data
}
}
internal func encode<T>(_ value: T) -> Data {
var value = value
return withUnsafePointer(to: &value) { p in
NSData(bytes: p, length: MemoryLayout.size(ofValue: value)) as Data
}
}
internal func decode<T>(_ data: Data) -> T {
let pointer = UnsafeMutablePointer<T>.allocate(capacity: MemoryLayout<T.Type>.size)
(data as NSData).getBytes(pointer, length: MemoryLayout<T.Type>.size)
return pointer.move()
}
protocol FileProviderSMBHeader {
var protocolID: UInt32 { get }
static var protocolConst: UInt32 { get }
}
// SMB2 Types
struct SMB2 {
struct Header: FileProviderSMBHeader { // 64 bytes
// header is always \u{fe}SMB
let protocolID: UInt32
static let protocolConst: UInt32 = 0x424d53fe
let size: UInt16
let creditCharge: UInt16
// error messages from the server to the client
let status: UInt32
enum StatusSeverity: UInt8 {
case success = 0, information, warning, error
}
var statusDetails: (severity: StatusSeverity, customer: Bool, facility: UInt16, code: UInt16) {
let severity = StatusSeverity(rawValue: UInt8(status >> 30))!
return (severity, status & 0x20000000 != 0, UInt16((status & 0x0FFF0000) >> 16), UInt16(status & 0x0000FFFF))
}
fileprivate let _command: UInt16
var command: Command {
get {
return Command(rawValue: _command) ?? .INVALID
}
}
let creditRequestResponse: UInt16
let flags: Flags
var nextCommand: UInt32
let messageId: UInt64
fileprivate let reserved: UInt32
let treeId: UInt32
var asyncId: UInt64 {
get {
return UInt64(reserved) + (UInt64(treeId) << 32)
}
}
let sessionId: UInt64
let signature: (UInt64, UInt64)
// codebeat:disable[ARITY]
init(command: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [], nextCommand: UInt32 = 0, messageId: UInt64, treeId: UInt32, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = type(of: self).protocolConst
self.size = 64
self.status = status.rawValue
self._command = command.rawValue
self.creditCharge = creditCharge
self.creditRequestResponse = creditRequestResponse
self.flags = flags
self.nextCommand = nextCommand
self.messageId = messageId
self.reserved = 0
self.treeId = treeId
self.sessionId = sessionId
self.signature = signature
}
init(asyncCommand: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [.ASYNC_COMMAND], nextCommand: UInt32 = 0, messageId: UInt64, asyncId: UInt64, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = type(of: self).protocolConst
self.size = 64
self.status = status.rawValue
self._command = asyncCommand.rawValue
self.creditCharge = creditCharge
self.creditRequestResponse = creditRequestResponse
self.flags = flags.union([Flags.ASYNC_COMMAND])
self.nextCommand = nextCommand
self.messageId = messageId
self.reserved = UInt32(asyncId & 0xffffffff)
self.treeId = UInt32(asyncId >> 32)
self.sessionId = sessionId
self.signature = signature
}
// codebeat:enable[ARITY]
}
struct Flags: OptionSet {
var rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
var priorityMask: UInt8 {
get {
return UInt8((rawValue & Flags.PRIORITY_MASK.rawValue) >> 4)
}
set {
rawValue = (rawValue & 0xffffff8f) | (UInt32(newValue & 0x7) << 4)
}
}
static let SERVER_TO_REDIR = Flags(rawValue: 0x00000001)
static let ASYNC_COMMAND = Flags(rawValue: 0x00000002)
static let RELATED_OPERATIONS = Flags(rawValue: 0x00000004)
static let SIGNED = Flags(rawValue: 0x00000008)
fileprivate static let PRIORITY_MASK = Flags(rawValue: 0x00000070)
static let DFS_OPERATIONS = Flags(rawValue: 0x10000000)
static let REPLAY_OPERATION = Flags(rawValue: 0x20000000)
}
enum Command: UInt16 {
case NEGOTIATE = 0x0000
case SESSION_SETUP = 0x0001
case LOGOFF = 0x0002
case TREE_CONNECT = 0x0003
case TREE_DISCONNECT = 0x0004
case CREATE = 0x0005
case CLOSE = 0x0006
case FLUSH = 0x0007
case READ = 0x0008
case WRITE = 0x0009
case LOCK = 0x000A
case IOCTL = 0x000B
case CANCEL = 0x000C
case ECHO = 0x000D
case QUERY_DIRECTORY = 0x000E
case CHANGE_NOTIFY = 0x000F
case QUERY_INFO = 0x0010
case SET_INFO = 0x0011
case OPLOCK_BREAK = 0x0012
case INVALID = 0xFFFF
}
// MARK: SMB2 Oplock Break
}
extension Data {
func subdata(in range: NSRange) -> Data {
return (self as NSData).subdata(with: range)
}
}
+208
View File
@@ -0,0 +1,208 @@
//
// SMBErrorType.swift
// ExtDownloader
//
// Created by Amir Abbas Mousavian on 4/30/95.
// Copyright © 1395 Mousavian. All rights reserved.
//
import Foundation
/// Error Types and Description
public enum NTStatus: UInt32, Error, CustomStringConvertible {
case SUCCESS = 0x00000000
case NOT_IMPLEMENTED = 0xC0000002
case INVALID_DEVICE_REQUEST = 0xC0000010
case ILLEGAL_FUNCTION = 0xC00000AF
case NO_SUCH_FILE = 0xC000000F
case NO_SUCH_DEVICE = 0xC000000E
case OBJECT_NAME_NOT_FOUND = 0xC0000034
case OBJECT_PATH_INVALID = 0xC0000039
case OBJECT_PATH_NOT_FOUND = 0xC000003A
case OBJECT_PATH_SYNTAX_BAD = 0xC000003B
case DFS_EXIT_PATH_FOUND = 0xC000009B
case REDIRECTOR_NOT_STARTED = 0xC00000FB
case TOO_MANY_OPENED_FILES = 0xC000011F
case ACCESS_DENIED = 0xC0000022
case INVALID_LOCK_SEQUENCE = 0xC000001E
case INVALID_VIEW_SIZE = 0xC000001F
case ALREADY_COMMITTED = 0xC0000021
case PORT_CONNECTION_REFUSED = 0xC0000041
case THREAD_IS_TERMINATING = 0xC000004B
case DELETE_PENDING = 0xC0000056
case PRIVILEGE_NOT_HELD = 0xC0000061
case LOGON_FAILURE = 0xC000006D
case FILE_IS_A_DIRECTORY = 0xC00000BA
case FILE_RENAMED = 0xC00000D5
case PROCESS_IS_TERMINATING = 0xC000010A
case DIRECTORY_NOT_EMPTY = 0xC0000101
case CANNOT_DELETE = 0xC0000121
case FILE_NOT_AVAILABLE = 0xC0000467
case FILE_DELETED = 0xC0000123
case SMB_BAD_FID = 0x00060001
case INVALID_HANDLE = 0xC0000008
case OBJECT_TYPE_MISMATCH = 0xC0000024
case PORT_DISCONNECTED = 0xC0000037
case INVALID_PORT_HANDLE = 0xC0000042
case FILE_CLOSED = 0xC0000128
case HANDLE_NOT_CLOSABLE = 0xC0000235
case SECTION_TOO_BIG = 0xC0000040
case TOO_MANY_PAGING_FILES = 0xC0000097
case INSUFF_SERVER_RESOURCES = 0xC0000205
case OS2_INVALID_ACCESS = 0x000C0001
case ACCESS_DENIED_2 = 0xC00000CA
case DATA_ERROR = 0xC000009C
case NOT_SAME_DEVICE = 0xC00000D4
case NO_MORE_FILES = 0x80000006
case NO_MORE_ENTRIES = 0x8000001A
case UNSUCCESSFUL = 0xC0000001
case SHARING_VIOLATION = 0xC0000043
case FILE_LOCK_CONFLICT = 0xC0000054
case LOCK_NOT_GRANTED = 0xC0000055
case END_OF_FILE = 0xC0000011
case NOT_SUPPORTED = 0xC00000BB
case OBJECT_NAME_COLLISION = 0xC0000035
case INVALID_PARAMETER = 0xC000000D
case OS2_INVALID_LEVEL = 0x007C0001
case OS2_NEGATIVE_SEEK = 0x00830001
case RANGE_NOT_LOCKED = 0xC000007E
case OS2_NO_MORE_SIDS = 0x00710001
case OS2_CANCEL_VIOLATION = 0x00AD0001
case OS2_ATOMIC_LOCKS_NOT_SUPPORTED = 0x00AE0001
case INVALID_INFO_CLASS = 0xC0000003
case INVALID_PIPE_STATE = 0xC00000AD
case INVALID_READ_MODE = 0xC00000B4
case OS2_CANNOT_COPY = 0x010A0001
case STOPPED_ON_SYMLINK = 0x8000002D
case INSTANCE_NOT_AVAILABLE = 0xC00000AB
case PIPE_NOT_AVAILABLE = 0xC00000AC
case PIPE_BUSY = 0xC00000AE
case PIPE_CLOSING = 0xC00000B1
case PIPE_EMPTY = 0xC00000D9
case PIPE_DISCONNECTED = 0xC00000B0
case BUFFER_OVERFLOW = 0x80000005
case MORE_PROCESSING_REQUIRED = 0xC0000016
case EA_TOO_LARGE = 0xC0000050
case OS2_EAS_DIDNT_FIT = 0x01130001
case EAS_NOT_SUPPORTED = 0xC000004F
case EA_LIST_INCONSISTENT = 0x80000014
case OS2_EA_ACCESS_DENIED = 0x03E20001
case NOTIFY_ENUM_DIR = 0x0000010C
case INVALID_SMB = 0x00010002
case WRONG_PASSWORD = 0xC000006A
case PATH_NOT_COVERED = 0xC0000257
case NETWORK_NAME_DELETED = 0xC00000C9
case SMB_BAD_TID = 0x00050002
case BAD_NETWORK_NAME = 0xC00000CC
case BAD_DEVICE_TYPE = 0xC00000CB
case SMB_BAD_COMMAND = 0x00160002
case PRINT_QUEUE_FULL = 0xC00000C6
case NO_SPOOL_SPACE = 0xC00000C7
case PRINT_CANCELLED = 0xC00000C8
case UNEXPECTED_NETWORK_ERROR = 0xC00000C4
case IO_TIMEOUT = 0xC00000B5
case REQUEST_NOT_ACCEPTED = 0xC00000D0
case TOO_MANY_SESSIONS = 0xC00000CE
case SMB_BAD_UID = 0x005B0002
case SMB_USE_MPX = 0x00FA0002
case SMB_USE_STANDARD = 0x00FB0002
case SMB_CONTINUE_MPX = 0x00FC0002
case ACCOUNT_DISABLED = 0xC0000072
case ACCOUNT_EXPIRED = 0xC0000193
case INVALID_WORKSTATION = 0xC0000070
case INVALID_LOGON_HOURS = 0xC000006F
case PASSWORD_EXPIRED = 0xC0000071
case PASSWORD_MUST_CHANGE = 0xC0000224
case SMB_NO_SUPPORT = 0xFFFF0002
case MEDIA_WRITE_PROTECTED = 0xC00000A2
case NO_MEDIA_IN_DEVICE = 0xC0000013
case INVALID_DEVICE_STATE = 0xC0000184
case DATA_ERROR_2 = 0xC000003E
case CRC_ERROR = 0xC000003F
case DISK_CORRUPT_ERROR = 0xC0000032
case NONEXISTENT_SECTOR = 0xC0000015
case DEVICE_PAPER_EMPTY = 0x8000000E
case WRONG_VOLUME = 0xC0000012
case DISK_FULL = 0xC000007F
case BUFFER_TOO_SMALL = 0xC0000023
case BAD_IMPERSONATION_LEVEL = 0xC00000A5
case USER_SESSION_DELETED = 0xC0000203
case NETWORK_SESSION_EXPIRED = 0xC000035C
case SMB_TOO_MANY_UIDS = 0xC000205A
public var description: String {
switch self {
case .NOT_IMPLEMENTED, .INVALID_DEVICE_REQUEST, .ILLEGAL_FUNCTION:
return "Invalid Function."
case .NO_SUCH_FILE, .NO_SUCH_DEVICE, .OBJECT_NAME_NOT_FOUND:
return "File not found."
case .OBJECT_PATH_INVALID, .OBJECT_PATH_NOT_FOUND, .OBJECT_PATH_SYNTAX_BAD, .DFS_EXIT_PATH_FOUND, .REDIRECTOR_NOT_STARTED:
return "A component in the path prefix is not a directory."
case .TOO_MANY_OPENED_FILES:
return "Too many open files. No FIDs are available."
case .ACCESS_DENIED, .INVALID_LOCK_SEQUENCE, .INVALID_VIEW_SIZE, .ALREADY_COMMITTED, .PORT_CONNECTION_REFUSED, .THREAD_IS_TERMINATING, .DELETE_PENDING, .PRIVILEGE_NOT_HELD, .LOGON_FAILURE, .FILE_IS_A_DIRECTORY, .FILE_RENAMED, .PROCESS_IS_TERMINATING, .CANNOT_DELETE, .FILE_DELETED:
return "Access denied."
case .SMB_BAD_FID, .INVALID_HANDLE, .OBJECT_TYPE_MISMATCH, .PORT_DISCONNECTED, .INVALID_PORT_HANDLE, .FILE_CLOSED, .HANDLE_NOT_CLOSABLE:
return "Invalid FID."
case .SECTION_TOO_BIG, .TOO_MANY_PAGING_FILES, .INSUFF_SERVER_RESOURCES:
return "Insufficient server memory to perform the requested operation."
case .OS2_INVALID_ACCESS:
return "Invalid open mode."
case .DATA_ERROR:
return "Bad data. (May be generated by IOCTL calls on the server.)"
case .DIRECTORY_NOT_EMPTY:
return "Remove of directory failed because it was not empty."
case .NOT_SAME_DEVICE:
return "A file system operation (such as a rename) across two devices was attempted."
case .NO_MORE_FILES:
return "No (more) files found following a file search command."
case .UNSUCCESSFUL:
return "General error."
case .SHARING_VIOLATION:
return "Sharing violation. A requested open mode conflicts with the sharing mode of an existing file handle."
case .FILE_LOCK_CONFLICT, .LOCK_NOT_GRANTED:
return "A lock request specified an invalid locking mode, or conflicted with an existing file lock."
case .END_OF_FILE:
return "Attempted to read beyond the end of the file."
case .NOT_SUPPORTED:
return "This command is not supported by the server."
case .OBJECT_NAME_COLLISION:
return "An attempt to create a file or directory failed because an object with the same pathname already exists."
case .INVALID_PARAMETER:
return "A parameter supplied with the message is invalid."
case .OS2_INVALID_LEVEL:
return "Invalid information level."
case .OS2_NEGATIVE_SEEK:
return "An attempt was made to seek to a negative absolute offset within a file."
case .RANGE_NOT_LOCKED:
return "The byte range specified in an unlock request was not locked."
case .OS2_NO_MORE_SIDS:
return "Maximum number of searches has been exhausted."
case .OS2_CANCEL_VIOLATION:
return "No lock request was outstanding for the supplied cancel region."
case .OS2_ATOMIC_LOCKS_NOT_SUPPORTED:
return "The file system does not support atomic changes to the lock type."
case .INVALID_INFO_CLASS, .INVALID_PIPE_STATE, .INVALID_READ_MODE:
return "Invalid named pipe."
case .OS2_CANNOT_COPY:
return "The copy functions cannot be used."
case .INSTANCE_NOT_AVAILABLE, .PIPE_NOT_AVAILABLE, .PIPE_BUSY:
return "All instances of the designated named pipe are busy."
case .PIPE_CLOSING, .PIPE_EMPTY:
return "The designated named pipe is in the process of being closed."
case .PIPE_DISCONNECTED:
return "The designated named pipe exists, but there is no server process listening on the server side."
case .BUFFER_OVERFLOW, .MORE_PROCESSING_REQUIRED:
return "There is more data available to read on the designated named pipe."
case .EA_TOO_LARGE, .OS2_EAS_DIDNT_FIT:
return "Either there are no extended attributes, or the available extended attributes did not fit into the response."
case .EAS_NOT_SUPPORTED:
return "The server file system does not support Extended Attributes."
case .OS2_EA_ACCESS_DENIED:
return "Access to the extended attribute was denied."
default:
return ""
}
}
}
+179
View File
@@ -0,0 +1,179 @@
//
// SessionDelegate.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate {
weak var fileProvider: FileProvider?
var credential: URLCredential?
var finishDownloadHandler: ((_ session: Foundation.URLSession, _ downloadTask: URLSessionDownloadTask, _ didFinishDownloadingToURL: URL) -> Void)?
var didSendDataHandler: ((_ session: Foundation.URLSession, _ task: URLSessionTask, _ bytesSent: Int64, _ totalBytesSent: Int64, _ totalBytesExpectedToSend: Int64) -> Void)?
var didReceivedData: ((_ session: Foundation.URLSession, _ downloadTask: URLSessionDownloadTask, _ bytesWritten: Int64, _ totalBytesWritten: Int64, _ totalBytesExpectedToWrite: Int64) -> Void)?
init(fileProvider: FileProvider, credential: URLCredential?) {
self.fileProvider = fileProvider
self.credential = credential
}
// codebeat:disable[ARITY]
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
self.finishDownloadHandler?(session, downloadTask, location)
return
}
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
self.didSendDataHandler?(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperation
switch type {
case "Create":
op = .create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .move(source: source, destination: dest)
case "Modify":
op = .modify(path: source)
case "Remove":
op = .remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .link(link: source, target: dest)
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
fileProvider?.delegate?.fileproviderProgress(fileProvider!, operation: op, progress: progress)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
self.didReceivedData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, let dest = json["dest"] as? String else {
return
}
fileProvider?.delegate?.fileproviderProgress(fileProvider!, operation: .copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let deposition: Foundation.URLSession.AuthChallengeDisposition = credential != nil ? .useCredential : .performDefaultHandling
completionHandler(deposition, credential)
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let deposition: Foundation.URLSession.AuthChallengeDisposition = credential != nil ? .useCredential : .performDefaultHandling
completionHandler(deposition, credential)
}
}
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
fileprivate static let status1xx = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
fileprivate 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"]
fileprivate 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"]
fileprivate 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"]
fileprivate 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"
}
}
}
+529
View File
@@ -0,0 +1,529 @@
//
// WebDAVFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
public final class WebDavFileObject: FileObject {
public let contentType: String
public let entryTag: String?
// codebeat:disable[ARITY]
public init(absoluteURL: URL, name: String, path: String, size: Int64 = -1, contentType: String = "", createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false, entryTag: String? = nil) {
self.contentType = contentType
self.entryTag = entryTag
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
// codebeat:enable[ARITY]
}
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
// in case of using this class with unencrypted HTTP connection.
open class WebDAVFileProvider: NSObject, FileProviderBasic {
open static let type: String = "WebDAV"
open let isPathRelative: Bool = true
open let baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
}
}
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
fileprivate var _session: URLSession?
fileprivate var sessionDelegate: SessionDelegate?
fileprivate var session: URLSession {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = OperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = URLSession(configuration: URLSessionConfiguration.default, delegate: sessionDelegate as URLSessionDownloadDelegate?, delegateQueue: queue)
}
return _session!
}
public init? (baseURL: URL, credential: URLCredential?) {
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
return nil
}
self.baseURL = baseURL
dispatch_queue = DispatchQueue(label: "FileProvider.\(WebDAVFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.credential = credential
}
deinit {
_session?.invalidateAndCancel()
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
let url = absoluteURL(path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
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>".data(using: String.Encoding.utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , 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]()
for attr in xresponse {
if attr.href.path == url.path {
continue
}
fileObjects.append(self.mapToFileObject(attr))
}
completionHandler(fileObjects, responseError ?? error)
return
}
completionHandler([], responseError ?? error)
})
task.resume()
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
let url = absoluteURL(path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
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>".data(using: String.Encoding.utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode, 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(self.mapToFileObject(attr), responseError ?? error)
return
}
}
completionHandler(nil, responseError ?? error)
})
task.resume()
}
open func storageProperties(completionHandler: @escaping ((_ 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
}
var request = URLRequest(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>".data(using: String.Encoding.utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
let task = session.dataTask(with: request, completionHandler: { (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(totalSize ?? -1, usedSize ?? 0)
return
}
}
completionHandler(-1, 0)
})
task.resume()
}
open weak var fileOperationDelegate: FileOperationDelegate?
}
extension WebDAVFileProvider: FileProviderOperations {
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")) ?? true == true else {
return
}
let url = absoluteURL((atPath as NSString).appendingPathComponent(folderName) + "/")
var request = URLRequest(url: url)
request.httpMethod = "MKCOL"
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) , code != .ok {
completionHandler?(FileProviderWebDavError(code: code, url: url))
return
}
completionHandler?(responseError ?? error)
self.delegateNotify(.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"), error: responseError ?? error)
})
task.resume()
}
public func create(file fileAttribs: FileObject, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .create(path: path)) ?? true == true else {
return
}
let url = absoluteURL(path)
var request = URLRequest(url: url)
request.httpMethod = "PUT"
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler?(responseError ?? error)
self.delegateNotify(.create(path: (path as NSString).appendingPathComponent(fileAttribs.name)), error: responseError ?? error)
})
task.taskDescription = dictionaryToJSON(["type": "Create" as NSString, "source": (path as NSString).appendingPathComponent(fileAttribs.name) as NSString])
task.resume()
}
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .move(source: path, destination: toPath)) ?? true == true else {
return
}
self.copyMoveItem(move: true, path: path, toPath: toPath, overwrite: overwrite, completionHandler: completionHandler)
}
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .copy(source: path, destination: toPath)) ?? true == true else {
return
}
self.copyMoveItem(move: false, path: path, toPath: toPath, overwrite: overwrite, completionHandler: completionHandler)
}
fileprivate func copyMoveItem(move:Bool, path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(path)
var request = URLRequest(url: url)
if move {
request.httpMethod = "MOVE"
} else {
request.httpMethod = "COPY"
}
request.setValue(absoluteURL(path).absoluteString, forHTTPHeaderField: "Destination")
if !overwrite {
request.setValue("F", forHTTPHeaderField: "Overwrite")
}
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let response = response as? HTTPURLResponse, 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)
}
if code == .multiStatus, let data = data {
let xresponses = self.parseXMLResponse(data)
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
completionHandler?(FileProviderWebDavError(code: code, url: url))
}
} else {
completionHandler?(FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error)
})
task.resume()
}
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .remove(path: path)) ?? true == true else {
return
}
let url = absoluteURL(path)
var request = URLRequest(url: url)
request.httpMethod = "DELETE"
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
defer {
self.delegateNotify(.remove(path: path), error: error)
}
if code == .multiStatus, let data = data {
let xresponses = self.parseXMLResponse(data)
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
completionHandler?(FileProviderWebDavError(code: code, url: url))
}
} else {
completionHandler?(FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error)
})
task.resume()
}
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .copy(source: localFile.absoluteString, destination: toPath)) ?? true == true else {
return
}
let url = absoluteURL(toPath)
var request = URLRequest(url: url)
request.httpMethod = "PUT"
let task = session.uploadTask(with: request, fromFile: localFile, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler?(responseError ?? error)
self.delegateNotify(.move(source: localFile.absoluteString, destination: toPath), error: responseError ?? error)
})
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": localFile.absoluteString as NSString, "dest": toPath as NSString])
task.resume()
}
public func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .copy(source: path, destination: toLocalURL.absoluteString)) ?? true == true else {
return
}
let url = absoluteURL(path)
let request = URLRequest(url: url)
let task = session.downloadTask(with: request, completionHandler: { (sourceFileURL, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let sourceFileURL = sourceFileURL {
do {
try FileManager.default.copyItem(at: sourceFileURL, to: toLocalURL)
} catch let e {
completionHandler?(e)
return
}
}
completionHandler?(responseError ?? error)
})
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": path as NSString, "dest": toLocalURL.absoluteString as NSString])
task.resume()
}
}
extension WebDAVFileProvider: FileProviderReadWrite {
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
}
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
let url = absoluteURL(path)
var request = URLRequest(url: url)
request.httpMethod = "GET"
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler(data, responseError ?? error)
})
task.resume()
}
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: .modify(path: path)) ?? true == true else {
return
}
// FIXME: lock destination before writing process
let url = atomically ? absoluteURL(path).appendingPathExtension("tmp") : absoluteURL(path)
var request = URLRequest(url: url)
request.httpMethod = "PUT"
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: self.absoluteURL(path))
}
defer {
self.delegateNotify(.modify(path: path), error: responseError ?? error)
}
if let error = error {
completionHandler?(error)
return
}
if atomically {
self.moveItem(path: (path as NSString).appendingPathExtension("tmp")!, to: path, completionHandler: completionHandler)
}
})
task.taskDescription = dictionaryToJSON(["type": "Modify" as NSString, "source": path as NSString])
task.resume()
}
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
let url = absoluteURL(path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
//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>".data(using: String.Encoding.utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
// FIXME: paginating results
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , 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]()
for attr in xresponse {
let path = attr.href.path
if !((path as NSString).lastPathComponent.contains(query)) {
continue
}
let fileObject = self.mapToFileObject(attr)
fileObjects.append(fileObject)
foundItemHandler?(fileObject)
}
completionHandler(fileObjects, responseError ?? error)
return
}
completionHandler([], responseError ?? error)
})
task.resume()
}
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is no unified api for monitoring WebDAV server content change/update
* Microsoft Exchange uses SUBSCRIBE method, Apple uses push notification system.
* while both is unavailable in a mobile platform.
* A messy approach is listing a directory with an interval period and compare
* with previous results
*/
}
fileprivate func unregisterNotifcation(path: String) {
}
// TODO: implements methods for lock mechanism
}
extension WebDAVFileProvider: FileProvider {}
// MARK: WEBDAV XML response implementation
internal extension WebDAVFileProvider {
struct DavResponse {
let href: URL
let hrefString: String
let status: Int?
let prop: [String: String]
}
fileprivate func parseXMLResponse(_ response: Data) -> [DavResponse] {
var result = [DavResponse]()
do {
let xml = try AEXMLDocument(xml: response)
var rootnode = xml.root
var responsetag = "response"
for node in rootnode.all ?? [] where node.name.lowercased().hasSuffix("multistatus") {
rootnode = node
}
for node in rootnode.children where node.name.lowercased().hasSuffix("response") {
responsetag = node.name
break
}
for responseNode in rootnode[responsetag].all ?? [] {
if let davResponse = mapNodeToDavResponse(responseNode) {
result.append(davResponse)
}
}
} catch _ {
}
return result
}
fileprivate func mapNodeToDavResponse(_ node: AEXMLElement) -> DavResponse? {
var hreftag = "href"
var statustag = "status"
var propstattag = "propstat"
for node in node.children {
if node.name.lowercased().hasSuffix("href") {
hreftag = node.name
}
if node.name.lowercased().hasSuffix("status") {
statustag = node.name
}
if node.name.lowercased().hasSuffix("propstat") {
propstattag = node.name
}
}
let href = node[hreftag].value
if let href = href, let hrefURL = URL(string: href) {
var status: Int?
let statusDesc = (node[statustag].string).components(separatedBy: " ")
if statusDesc.count > 2 {
status = Int(statusDesc[1])
}
var propDic = [String: String]()
let propStatNode = node[propstattag]
for node in propStatNode.children where node.name.lowercased().hasSuffix("status"){
statustag = node.name
break
}
let statusDesc2 = (propStatNode[statustag].string).components(separatedBy: " ")
if statusDesc2.count > 2 {
status = Int(statusDesc2[1])
}
var proptag = "prop"
for tnode in propStatNode.children where tnode.name.lowercased().hasSuffix("prop") {
proptag = tnode.name
break
}
for propItemNode in propStatNode[proptag].children {
propDic[propItemNode.name.components(separatedBy: ":").last!.lowercased()] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xml.contains("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
}
}
return DavResponse(href: hrefURL, hrefString: href, status: status, prop: propDic)
}
return nil
}
fileprivate func mapToFileObject(_ davResponse: DavResponse) -> WebDavFileObject {
var href = davResponse.href
if href.baseURL == nil {
href = absoluteURL(href.path)
}
let name = davResponse.prop["displayname"] ?? (davResponse.hrefString.removingPercentEncoding! as NSString).lastPathComponent
let size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
let createdDate = self.resolve(dateString: davResponse.prop["creationdate"] ?? "")
let modifiedDate = self.resolve(dateString: davResponse.prop["getlastmodified"] ?? "")
let contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
let isDirectory = contentType == "httpd/unix-directory"
let entryTag = davResponse.prop["getetag"]
return WebDavFileObject(absoluteURL: href, name: name, path: href.path, size: size, contentType: contentType, createdDate: createdDate, modifiedDate: modifiedDate, fileType: isDirectory ? .directory : .regular, isHidden: false, isReadOnly: false, entryTag: entryTag)
}
fileprivate func delegateNotify(_ operation: FileOperation, error: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
self.delegate?.fileproviderFailed(self, operation: operation)
}
})
}
}
public struct FileProviderWebDavError: Error, CustomStringConvertible {
public let code: FileProviderHTTPErrorCode
public let url: URL
public var description: String {
return code.description
}
}