// // BloggenUploadHelperUploader.m // Bloggen // // Created by Davis Remmel on 2/14/14. // Copyright (c) 2014 Davis Remmel. All rights reserved. // #import "BloggenUploadHelperUploader.h" #import "BloggenSandboxUtilities.h" #import "BloggenAWSUtilities.h" @implementation BloggenUploadHelperUploader - (void)rsyncDirectoryBookmark:(NSData *)directoryBookmark withUsername:(NSString *)username withSSHBookmark:(NSData *)sshBookmark withKeyFileBookmark:(NSData *)keyFileBookmark toServer:(NSString *)server exitStatus:(void (^)(int))exitStatus { // Because this XPC service runs in a different sandbox than the main // app, it won't be able to access its security-scoped files. Therefore, // we need to generate a new security-scoped bookmark and URL for any // files this needs to access. NSURL *directoryURL = [BloggenSandboxUtilities urlForBookmark:directoryBookmark]; NSData *directorySSBookmark = [BloggenSandboxUtilities ssBookmarkForURL:directoryURL]; NSURL *directorySSURL = [BloggenSandboxUtilities ssURLForBookmark:directorySSBookmark]; NSURL *sshDirectoryURL = [BloggenSandboxUtilities urlForBookmark:sshBookmark]; NSData *sshDirectorySSBookmark = [BloggenSandboxUtilities ssBookmarkForURL:sshDirectoryURL]; NSURL *sshDirectorySSURL = [BloggenSandboxUtilities ssURLForBookmark:sshDirectorySSBookmark]; NSString *pathToSSHConfigFile = [[sshDirectoryURL path] stringByAppendingPathComponent:@"config"]; NSURL *keyFileURL = [BloggenSandboxUtilities urlForBookmark:keyFileBookmark]; NSData *keyFileSSBookmark = [BloggenSandboxUtilities ssBookmarkForURL:keyFileURL]; NSURL *keyFileSSURL = [BloggenSandboxUtilities ssURLForBookmark:keyFileSSBookmark]; // Did the user enter a path with the server? If not, append a colon to the server for default remote directory. if ([[server componentsSeparatedByString:@":"] count] < 2) { server = [server stringByAppendingString:@":"]; } NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/usr/bin/rsync"]; // Add task arguments, depending on if using a key file NSMutableArray *taskArguments = [[NSMutableArray alloc] init]; [taskArguments addObject:@"-avzhq"]; if ([[keyFileSSURL path] length]) { [taskArguments addObject:@"-e"]; [taskArguments addObject:[NSString stringWithFormat:@"ssh -i %@ -F %@", [keyFileSSURL path], pathToSSHConfigFile]]; } [taskArguments addObject:[[directorySSURL path] stringByAppendingString:@"/"]]; // Trailing slash uploads files, not directory [taskArguments addObject:[NSString stringWithFormat:@"%@@%@", username, server]]; [task setArguments:[taskArguments copy]]; if ([sshDirectorySSURL startAccessingSecurityScopedResource]) { [task launch]; //NSLog(@"Uploading: %@", [directoryPath lastPathComponent]); [task waitUntilExit]; [sshDirectorySSURL stopAccessingSecurityScopedResource]; } if (0 != [task terminationStatus]) { NSLog(@"Error in upload process: rsync did not terminate successfully. Failed file: %@", [directorySSURL path]); } [keyFileSSURL stopAccessingSecurityScopedResource]; exitStatus([task terminationStatus]); } - (void)ftpChangedFilesInArray:(NSArray *)changedFilesArary withDirectoryBookmark:(NSData *)directoryBookmark withUsername:(NSString *)username withPassword:(NSString *)password toServer:(NSString *)server error:(void (^)(int))returnError { // Because this XPC service runs in a different sandbox than the main // app, it won't be able to access its security-scoped files. Therefore, // we need to generate a new security-scoped bookmark and URL for any // files this needs to access. NSURL *directoryURL = [BloggenSandboxUtilities urlForBookmark:directoryBookmark]; NSData *directorySSBookmark = [BloggenSandboxUtilities ssBookmarkForURL:directoryURL]; NSURL *directorySSURL = [BloggenSandboxUtilities ssURLForBookmark:directorySSBookmark]; int errorCount = 0; // Increments if cURL has a termination status greater than zero, indicating an error for (NSArray *filePathArray in changedFilesArary) { NSString *relativeFilePath = [filePathArray objectAtIndex:0]; NSString *localFilePath = [filePathArray objectAtIndex:1]; NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/usr/bin/curl"]; [task setArguments:@[ @"-u", [NSString stringWithFormat:@"%@:%@", username, password], @"--ftp-create-dirs", @"--silent", @"-T", [NSString stringWithFormat:@"%@", localFilePath], [NSString stringWithFormat:@"ftp://%@/%@", server, relativeFilePath] ]]; if ([directorySSURL startAccessingSecurityScopedResource]) { [task launch]; //NSLog(@"Uploading: %@", relativeFilePath); [task waitUntilExit]; [directorySSURL stopAccessingSecurityScopedResource]; } if (0 != [task terminationStatus]) { NSLog(@"Error in upload process: cURL did not terminate successfully. Failed file: %@", localFilePath); } errorCount += [task terminationStatus]; } returnError(errorCount); } - (void)s3ChangedFilesInArray:(NSArray *)changedFilesArray withDirectoryBookmark:(NSData *)directoryBookmark withAccessKeyID:(NSString *)accessKeyID withSecretAccessKey:(NSString *)secretAccessKey toBucket:(NSString *)server error:(void (^)(int))returnError { // Because this XPC service runs in a different sandbox than the main // app, it won't be able to access its security-scoped files. Therefore, // we need to generate a new security-scoped bookmark and URL for any // files this needs to access. NSURL *directoryURL = [BloggenSandboxUtilities urlForBookmark:directoryBookmark]; NSData *directorySSBookmark = [BloggenSandboxUtilities ssBookmarkForURL:directoryURL]; NSURL *directorySSURL = [BloggenSandboxUtilities ssURLForBookmark:directorySSBookmark]; int errorCount = 0; // Increments if cURL has a termination status greater than zero, indicating an error // Get the bucket name NSMutableArray *serverParts = [[server componentsSeparatedByString:@"/"] mutableCopy]; NSString *s3Bucket = [serverParts firstObject]; // Get the path prefix (if the upload path isn't to the bucket's root) NSString *uploadPrefixPath = @""; if ([serverParts count] > 1) { [serverParts removeObjectAtIndex:0]; // Pop off the bucket name uploadPrefixPath = [[serverParts componentsJoinedByString:@"/"] stringByAppendingString:@"/"]; // Add a trailing slash } // Start instance stuff NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; [dateFormatter setLocale:enUSPOSIXLocale]; NSDate *nowDate = [NSDate date]; // Iterate through the files that have changed for (NSArray *filePathArray in changedFilesArray) { NSString *relativeFilePath = [filePathArray objectAtIndex:0]; NSString *localFilePath = [filePathArray objectAtIndex:1]; // Get the content-type NSString *fileExtension = [localFilePath pathExtension]; NSString *fileContentType = @"binary/octet-stream"; // Default content type if a match isn't found // Put known file-types and their content-types into a dictionary NSDictionary *typeDictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"S3ContentTypes" ofType:@"plist"]]; // Find the extension in the dictionary for (NSString *keyExtension in typeDictionary) { NSString *contentType = [typeDictionary valueForKey:keyExtension]; if ([keyExtension isEqualToString:fileExtension]) { fileContentType = contentType; } } // Make the policy [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; NSString *expirationDateString = [dateFormatter stringFromDate:[nowDate dateByAddingTimeInterval:60*60*24*1]]; NSString *policyString = [NSString stringWithFormat:@"{ \"expiration\": \"%@\",\n \"conditions\": [\n {\"bucket\": \"%@\"},\n [\"starts-with\", \"$key\", \"%@%@\"],\n {\"acl\": \"public-read\"},\n [\"starts-with\", \"$Content-Type\", \"%@\"],\n ]\n}", expirationDateString, s3Bucket, uploadPrefixPath, relativeFilePath, fileContentType]; NSData *policyData = [policyString dataUsingEncoding:NSUTF8StringEncoding]; NSString *policyBase64String = [policyData base64EncodedStringWithOptions:0]; // Sign the policy NSData *secretKeyData = [secretAccessKey dataUsingEncoding:NSUTF8StringEncoding]; NSData *signatureData = [BloggenAWSUtilities hmacSHA1WithDigestKey:secretKeyData stringToEncode:policyBase64String]; NSString *signatureString = [signatureData base64EncodedStringWithOptions:0]; // Ready to upload NSTask *task = [[NSTask alloc] init]; NSPipe *stdoutPipe = [NSPipe pipe]; [task setLaunchPath:@"/usr/bin/curl"]; [task setArguments:@[ @"-F", [NSString stringWithFormat:@"key=%@%@", uploadPrefixPath, relativeFilePath], @"-F", [NSString stringWithFormat:@"acl=public-read"], @"-F", [NSString stringWithFormat:@"AWSAccessKeyId=%@", accessKeyID], @"-F", [NSString stringWithFormat:@"Policy=%@", policyBase64String], @"-F", [NSString stringWithFormat:@"Signature=%@", signatureString], @"-F", [NSString stringWithFormat:@"Content-Type=%@", fileContentType], @"-F", [NSString stringWithFormat:@"file=@%@", localFilePath], @"--silent", [NSString stringWithFormat:@"http://%@.s3.amazonaws.com", s3Bucket] ]]; [task setStandardOutput:stdoutPipe]; if ([directorySSURL startAccessingSecurityScopedResource]) { [task launch]; //NSLog(@"Uploading: %@", relativeFilePath); [task waitUntilExit]; [directorySSURL stopAccessingSecurityScopedResource]; } NSFileHandle *curlResponse = [stdoutPipe fileHandleForReading]; NSData *curlResponseData = [curlResponse readDataToEndOfFile]; NSString *curlResponseString = [[NSString alloc] initWithData:curlResponseData encoding:NSUTF8StringEncoding]; // curlResponseString will only contain information if AWS responed with an error. if ([curlResponseString length]) { NSLog(@"Error uploading to S3: %@\nFailed file: %@", curlResponseString, localFilePath); ++errorCount; // cURL still exits normally, even if the request failed } // cURL may also not have finished for other reasons. if (0 != [task terminationStatus]) { NSLog(@"Error in upload process: cURL did not terminate successfully. Failed file: %@", localFilePath); } errorCount += [task terminationStatus]; } returnError(errorCount); } @end