ConnectionKit 2 is still under heavy development, but the front-end API is probably stable.
Things someone could do if they're feeling nice:
- Cancellation support for the File protocol
- Improve handling of invalid certificates for FTPS
- Amazon S3 protocol
- API for downloading/reading files
ConnectionKit provides a Cocoa-friendly, block-based API for asynchronously working with:
- FTP, SFTP and WebDAV servers
- Local files
A high-level summary:
- Use of blocks for simple completion and error-handling
- URLs replace paths throughout the API
- Management of raw connections is hidden behind the scenes, handling multiple connections and re-connections for you
- Same authentication workflow as
NSURLSession
and friends - No longer tied to the main thread
- libcurl is used for FTP, instead of custom implementation
NSURLConnection
(via DAVKit) is used for WebDAV, instead of custom HTTP stack- libssh2 (via libcurl) is used for SFTP, instead of calling out to the command-line
sftp
program
I'm Mike Abdullah, of Karelia Software. @mikeabdullah on Twitter.
Questions about the code are best left as issues at https://github.com/karelia/ConnectionKit but you can also message me on Twitter (just don't expect more than a terse reply!).
Big thanks to:
- Paul Kim of Noodlesoft for:
CK2OpenPanel
- Logic for guessing icon etc. of remote files
- Fabian Jäger for discovering and helping fix various bugs
- Sam Deane of Elegant Chaos for:
- Sooo much testing, especially Mock Server
- The WebDAV protocol implementation
- Improving the File protocol implementation
- And all contributors to the submodules of course!
Requires OS X v10.6+
Relies upon CURLHandle and DAVKit. They are provided as submodules and may have their own dependencies in turn. Out of the box, provided you initialise all submodules, CURLHandle.framework
should be able to nicely build, self-containing all its dependencies.
ConnectionKit supports both 64 and 32bit Macs. We hope to expand to iOS before too long too. Note that support for the legacy Objective-C runtime (32bit Mac) currently precludes switching the codebase to ARC.
Please see https://github.com/karelia/CurlHandle for details of CURLHandle and its subcomponents' licensing.
Please see https://github.com/karelia/DAVKit for details of DAVKit and its subcomponents' licensing.
Existing ConnectionKit code should declare its licensing at the top of the file, most likely BSD or MIT.
Licensed under the BSD License http://www.opensource.org/licenses/bsd-license THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
If you're already using Git, you likely want to add ConnectionKit as a submodule of your project:
git submodule add https://github.com/karelia/ConnectionKit.git
If you're using another version control system, you can grab the code directly:
git clone https://github.com/karelia/ConnectionKit.git
If you're not using version control at all, remind me to come yell at you at a mutually convenient time.
ConnectionKit includes several (nested) submodules of its own, so have Git grab them too:
cd ConnectionKit
git submodule update --recursive --init
Substitute the URL above for your own if you've created a fork of ConnectionKit. Git should automatically checkout the recommended branch for you (v2.x-beta
at present).
Then:
- Add
Connection.xcodeproj
to your project - Add the ConnectionKit framework as a dependency of your project's build target
- Set
Connection.framework
to be copied into a suitable location inside your build target; e.g theFrameworks
directory
Interacting with ConnectionKit is usually entirely through CK2FileManager
. It's quite a lot like NSFileManager
, but asynchronous, and with a few more bells and whistles to handle the complexities of remote servers. Also there's no shared instance; you must create your own.
So to get a directory listing from an FTP server for example:
- (void)listDirectoryAtPath:(NSString *)path
{
NSURL *ftpServer = [NSURL URLWithString:@"ftp://example.com/"];
NSURL *directory = [CK2FileManager URLWithPath:path relativeToURL:ftpServer];
CK2FileManager *fileManager = [[CK2FileManager alloc] init];
fileManager.delegate = self;
[fileManager contentsOfDirectoryAtURL:directory
includingPropertiesForKeys:nil
options:NSDirectoryEnumerationSkipsHiddenFiles
completionHandler:^(NSArray *contents, NSError *error) {
// Display contents in your UI, or present the error if failed
}];
}
Note how CK2FileManager
is used to construct URLs. This is to handle the difference in URL formats different protocols require.
Delegate methods are used to handle authentication (more on that below) and transcripts. Be sure to read through CK2FileManager.h
as there's plenty of helpful documentation in there.
ConnectionKit follows the same approach as NSURLSession
: During an operation, it vends out as many authentication challenges as it sees fit. Your delegate should implement this method to reply to the challenges:
- (void)fileManager:(CK2FileManager *)manager
operation:(CK2FileOperation *)operation
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential*))completionHandler;
Replies are made by calling completionHandler
with your preferred disposition, and, if needed, a credential to use. This is asynchronous, giving you a chance to present some UI asking the user what they'd like to do if necessary.
Supplying a credential set to NSURLCredentialPersistencePermanent
will cause ConnectionKit to add it to the keychain if successful.
Authentication challenges carry a great deal of information, including .previousFailureCount
and .protectionSpace
which are very useful for determining how to treat an individual challenge.
WebDAV servers can selectively choose whether to require authentication (e.g. public servers have no need to). If authentication is requested, you'll receive an authentication challenge encapsulating the auth method to be used (e.g. HTTP Digest). Respond with a username and password credential. ConnectionKit will do its best to supply -proposedCredential
from the user's keychain.
FTP is very similar to plain WebDAV, except it always asks for authentication. Usually, you respond with a username and password, but can pass a nil
credential for anonymous FTP login.
The authenticity of the server is checked by examining its certificate during connection. This takes the form of potentially multiple challenges with the either of the following authentication methods:
NSURLAuthenticationMethodServerTrust
NSURLAuthenticationMethodClientCertificate
Generally it's best to use CK2AuthChallengePerformDefaultHandling
to let Cocoa decide what to do.
SFTP is a tricky blighter. You can opt to supply a username and password like other protocols. Our implementation also supports public key authentication, whereby you reply with a credential constructed using:
+[NSURLCredential ck2_credentialWithUser:publicKeyURL:privateKeyURL:password:persistence:]
The public key is generally optional, as ConnectionKit can derive it from the private key. It's also possible to use SSH-Agent, but Apple discourage this, and it is unavailable to sandboxed apps. Detailed documentation on the above method can be found in CK2Authentication.h
.
Once connected to the server, ConnectionKit checks its fingerprint against the ~/.ssh/known_hosts
file. Note that for sandboxed apps this is inside of your container! An authentication challenge (CK2AuthenticationMethodHostFingerprint
) is issued with the result of this. Your delegate can use CK2AuthChallengeCancelAuthenticationChallenge
to reject the fingerprint, or reply with a credential for acceptance, constructed using:
+[NSURLCredential ck2_credentialForKnownHostWithPersistence:]
The default behaviour (CK2AuthChallengePerformDefaultHandling
) accepts new fingerprints, adding them to the known_hosts
file, and causes the operation to fail with an error for mismatched fingerprints.
After checking the host fingerprint, SFTP moves on to actually authenticating the client.
ConnectionKit's API is a little asymmetric for handling resource properties:
When creating a file or directory, opening attributes may be specified. Generally only NSFilePosixPermissions
is respected. This only applies to protocols where permissions can be specified at creation time (i.e. SFTP). But even then there are some servers in my experience that sometimes ignore this value anyway. So:
To set the properties of an existing item, use -[CK2FileManager setAttributes:ofItemAtURL:completionHandler:]
. Again this only applies to certain protocols/servers; see CK2FileManager.h
for up-to-date information on this.
Many protocols do not have an efficient mechanism for retrieving the attributes of an individual item. Instead, you should get a listing of the parent directory, and pull out the properties of whichever resources you're interested in.
ConnectionKit also offers a companion framework for OS X: ConnectionKitUI
. If you build this framework into your app as well as ConnectionKit itself, it exposes CK2OpenPanel
, an NSOpenPanel
workalike for browsing and selecting files on remote servers.
For anyone relying on one of the old branches, they have been archived to be tags:
- master => v1.x
- release-1.2 => v1.2.x
- BrianWorkInProgress => brian-work-in-progress
- CKFTPResponse => ckftpresponse
- release-2.0 => experiment-2.0