commit 4a346a274cd901d4645b51e6f0bce20bc5d3fcba Author: Stefan Reitshamer Date: Wed Mar 31 08:57:26 2010 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/ArqFolder.h b/ArqFolder.h new file mode 100644 index 0000000..d8bc051 --- /dev/null +++ b/ArqFolder.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "DictNode.h" + +@interface ArqFolder : NSObject { + NSString *s3Path; + NSString *localPath; +} +- (id)initWithS3Path:(NSString *)theS3Path plist:(DictNode *)plist; +- (NSString *)s3Path; +- (NSString *)localPath; +@end diff --git a/ArqFolder.m b/ArqFolder.m new file mode 100644 index 0000000..a5303c9 --- /dev/null +++ b/ArqFolder.m @@ -0,0 +1,55 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "ArqFolder.h" +#import "DictNode.h" + +@implementation ArqFolder +- (id)initWithS3Path:(NSString *)theS3Path plist:(DictNode *)plist { + if (self = [super init]) { + s3Path = [theS3Path copy]; + localPath = [[[plist stringNodeForKey:@"LocalPath"] stringValue] copy]; + } + return self; +} +- (void)dealloc { + [s3Path release]; + [localPath release]; + [super dealloc]; +} +- (NSString *)s3Path { + return s3Path; +} +- (NSString *)localPath { + return localPath; +} +@end diff --git a/ArqRestoreCommand.h b/ArqRestoreCommand.h new file mode 100644 index 0000000..c874331 --- /dev/null +++ b/ArqRestoreCommand.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class S3Service; + +@interface ArqRestoreCommand : NSObject { + NSString *accessKey; + NSString *secretKey; + NSString *encryptionPassword; + S3Service *s3; +} +- (BOOL)printArqFolders:(NSError **)error; +- (BOOL)restorePath:(NSString *)path error:(NSError **)error; +@end diff --git a/ArqRestoreCommand.m b/ArqRestoreCommand.m new file mode 100644 index 0000000..fcfe1b1 --- /dev/null +++ b/ArqRestoreCommand.m @@ -0,0 +1,191 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "ArqRestoreCommand.h" +#import "SetNSError.h" +#import "S3AuthorizationProvider.h" +#import "S3Service.h" +#import "RegexKitLite.h" +#import "DictNode.h" +#import "ArqFolder.h" +#import "HTTP.h" +#import "Restorer.h" + +@interface ArqRestoreCommand (internal) +- (BOOL)validateS3Keys:(NSError **)error; +- (NSArray *)s3BucketNames:(NSError **)error; +- (NSString *)s3BucketNameForRegion:(int)s3BucketRegion; +@end + +@implementation ArqRestoreCommand +- (id)init { + if (self = [super init]) { + char *theAccessKey = getenv("ARQ_ACCESS_KEY"); + if (theAccessKey != NULL) { + accessKey = [[NSString alloc] initWithUTF8String:theAccessKey]; + } + char *theSecretKey = getenv("ARQ_SECRET_KEY"); + if (theSecretKey != NULL) { + secretKey = [[NSString alloc] initWithUTF8String:theSecretKey]; + } + char *theEncryptionPassword = getenv("ARQ_ENCRYPTION_PASSWORD"); + if (theEncryptionPassword != NULL) { + encryptionPassword = [[NSString alloc] initWithUTF8String:theEncryptionPassword]; + } + if (accessKey != nil && secretKey != nil) { + S3AuthorizationProvider *sap = [[S3AuthorizationProvider alloc] initWithAccessKey:accessKey secretKey:secretKey]; + s3 = [[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:NO retryOnNetworkError:YES]; + [sap release]; + } + } + return self; +} +- (void)dealloc { + [accessKey release]; + [secretKey release]; + [encryptionPassword release]; + [s3 release]; + [super dealloc]; +} +- (BOOL)printArqFolders:(NSError **)error { + if (![self validateS3Keys:error]) { + return NO; + } + NSArray *s3BucketNames = [self s3BucketNames:error]; + if (s3BucketNames == nil) { + return NO; + } + NSMutableArray *computerUUIDPaths = [NSMutableArray array]; + for (NSString *s3BucketName in s3BucketNames) { + NSString *computerUUIDPrefix = [NSString stringWithFormat:@"/%@/", s3BucketName]; + NSError *myError = nil; + NSArray *computerUUIDs = [s3 commonPrefixesForPathPrefix:computerUUIDPrefix delimiter:@"/" error:&myError]; + if (computerUUIDs == nil) { + if ([[myError domain] isEqualToString:[S3Service serverErrorDomain]] && [myError code] == HTTP_NOT_FOUND) { + // Skip. + } else { + if (error != NULL) { + *error = myError; + } + return NO; + } + } + for (NSString *computerUUID in computerUUIDs) { + [computerUUIDPaths addObject:[computerUUIDPrefix stringByAppendingPathComponent:computerUUID]]; + } + } + for (NSString *computerUUIDPath in computerUUIDPaths) { + NSString *computerBucketsPrefix = [computerUUIDPath stringByAppendingPathComponent:@"buckets"]; + NSArray *s3BucketUUIDPaths = [s3 pathsWithPrefix:computerBucketsPrefix error:error]; + if (s3BucketUUIDPaths == nil) { + return NO; + } + for (NSString *uuidPath in s3BucketUUIDPaths) { + NSData *data = [s3 dataAtPath:uuidPath error:error]; + if (data == nil) { + return NO; + } + DictNode *plist = [DictNode dictNodeWithXMLData:data error:error]; + if (plist == nil) { + return NO; + } + printf("s3 path=%s\tlocal path=%s\n", [uuidPath UTF8String], [[[plist stringNodeForKey:@"LocalPath"] stringValue] UTF8String]); + } + } + return YES; +} +- (BOOL)restorePath:(NSString *)path error:(NSError **)error { + if (![self validateS3Keys:error]) { + return NO; + } + if (encryptionPassword == nil) { + SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_ENCRYPTION_PASSWORD environment variable"); + return NO; + } + NSString *pattern = @"^/([^/]+)/([^/]+)/buckets/([^/]+)"; + NSRange s3BucketNameRange = [path rangeOfRegex:pattern capture:1]; + NSRange computerUUIDRange = [path rangeOfRegex:pattern capture:2]; + NSRange bucketUUIDRange = [path rangeOfRegex:pattern capture:3]; + if (s3BucketNameRange.location == NSNotFound || computerUUIDRange.location == NSNotFound || bucketUUIDRange.location == NSNotFound) { + SETNSERROR(@"ArqErrorDomain", -1, @"invalid S3 path"); + return NO; + } + NSData *data = [s3 dataAtPath:path error:error]; + if (data == nil) { + return NO; + } + DictNode *plist = [DictNode dictNodeWithXMLData:data error:error]; + if (plist == nil) { + return NO; + } + NSString *s3BucketName = [path substringWithRange:s3BucketNameRange]; + NSString *computerUUID = [path substringWithRange:computerUUIDRange]; + NSString *bucketUUID = [path substringWithRange:bucketUUIDRange]; + NSString *bucketName = [[plist stringNodeForKey:@"BucketName"] stringValue]; + Restorer *restorer = [[[Restorer alloc] initWithS3Service:s3 s3BucketName:s3BucketName computerUUID:computerUUID bucketUUID:bucketUUID bucketName:bucketName encryptionKey:encryptionPassword] autorelease]; + if (![restorer restore:error]) { + return NO; + } + printf("restored files are in %s\n", [bucketName fileSystemRepresentation]); + return YES; +} +@end + +@implementation ArqRestoreCommand (internal) +- (BOOL)validateS3Keys:(NSError **)error { + if (accessKey == nil) { + SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_ACCESS_KEY environment variable"); + return NO; + } + if (secretKey == nil) { + SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_SECRET_KEY environment variable"); + return NO; + } + return YES; +} +- (NSArray *)s3BucketNames:(NSError **)error { + return [NSArray arrayWithObjects: + [self s3BucketNameForRegion:BUCKET_REGION_US_STANDARD], + [self s3BucketNameForRegion:BUCKET_REGION_US_WEST], + [self s3BucketNameForRegion:BUCKET_REGION_EU], + nil]; +} +- (NSString *)s3BucketNameForRegion:(int)s3BucketRegion { + NSString *regionSuffix = @""; + if (s3BucketRegion == BUCKET_REGION_US_WEST) { + regionSuffix = @".us-west-1"; + } else if (s3BucketRegion == BUCKET_REGION_EU) { + regionSuffix = @".eu"; + } + return [NSString stringWithFormat:@"%@.com.haystacksoftware.arq%@", [accessKey lowercaseString], regionSuffix]; +} +@end diff --git a/ArqUserLibrary.h b/ArqUserLibrary.h new file mode 100644 index 0000000..a3bf2b3 --- /dev/null +++ b/ArqUserLibrary.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface ArqUserLibrary : NSObject { + +} ++ (NSString *)arqCachesPath; +@end diff --git a/ArqUserLibrary.m b/ArqUserLibrary.m new file mode 100644 index 0000000..9b69a95 --- /dev/null +++ b/ArqUserLibrary.m @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "ArqUserLibrary.h" + + +@implementation ArqUserLibrary ++ (NSString *)arqCachesPath { + return [NSHomeDirectory() stringByAppendingPathComponent:@"/Library/Arq/Caches.noindex"]; +} +@end diff --git a/Commit.h b/Commit.h new file mode 100644 index 0000000..5cee273 --- /dev/null +++ b/Commit.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "Blob.h" +#import "BufferedInputStream.h" + +#define CURRENT_COMMIT_VERSION 3 + +@interface Commit : NSObject { + int commitVersion; + NSString *_author; + NSString *_comment; + NSMutableSet *_parentCommitSHA1s; + NSString *_treeSHA1; + NSString *_location; + NSString *_computer; + NSString *_mergeCommonAncestorCommitSHA1; + NSDate *_creationDate; + NSArray *_commitFailedFiles; +} +- (id)initWithBufferedInputStream:(id )is error:(NSError **)error; + +@property(readonly,copy) NSString *author; +@property(readonly,copy) NSString *comment; +@property(readonly,copy) NSString *treeSHA1; +@property(readonly,retain) NSSet *parentCommitSHA1s; +@property(readonly,copy) NSString *location; +@property(readonly,copy) NSString *computer; +@property(readonly,copy) NSString *mergeCommonAncestorCommitSHA1; +@property(readonly,retain) NSDate *creationDate; +@property(readonly,retain) NSArray *commitFailedFiles; +@end diff --git a/Commit.m b/Commit.m new file mode 100644 index 0000000..9da6397 --- /dev/null +++ b/Commit.m @@ -0,0 +1,183 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "IntegerIO.h" +#import "DateIO.h" +#import "StringIO.h" +#import "Commit.h" +#import "Blob.h" +#import "DataInputStream.h" +#import "RegexKitLite.h" +#import "SetNSError.h" +#import "NSErrorCodes.h" +#import "CommitFailedFile.h" + +#define HEADER_LENGTH (10) + +@interface Commit (internal) +- (BOOL)readHeader:(id )is error:(NSError **)error; +@end + +@implementation Commit +@synthesize author = _author, +comment = _comment, +treeSHA1 = _treeSHA1, +parentCommitSHA1s = _parentCommitSHA1s, +location = _location, +computer = _computer, +mergeCommonAncestorCommitSHA1 = _mergeCommonAncestorCommitSHA1, +creationDate = _creationDate, +commitFailedFiles = _commitFailedFiles; + +- (id)initWithBufferedInputStream:(id )is error:(NSError **)error { + if (self = [super init]) { + _parentCommitSHA1s = [[NSMutableSet alloc] init]; + if (![self readHeader:is error:error]) { + [self release]; + return nil; + } + BOOL ret = NO; + do { + if (![StringIO read:&_author from:is error:error]) { + break; + } + [_author retain]; + + if (![StringIO read:&_comment from:is error:error]) { + break; + } + [_comment retain]; + + uint64_t parentCommitKeyCount = 0; + if (![IntegerIO readUInt64:&parentCommitKeyCount from:is error:error]) { + break; + } + for (uint64_t i = 0; i < parentCommitKeyCount; i++) { + NSString *key; + if (![StringIO read:&key from:is error:error]) { + break; + } + [_parentCommitSHA1s addObject:key]; + } + + if (![StringIO read:&_treeSHA1 from:is error:error]) { + break; + } + [_treeSHA1 retain]; + + if (![StringIO read:&_location from:is error:error]) { + break; + } + [_location retain]; + + NSRange computerRange = [_location rangeOfRegex:@"^file://([^/]+)/" capture:1]; + if (computerRange.location != NSNotFound) { + _computer = [[_location substringWithRange:computerRange] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + } else { + _computer = @""; + } + [_computer retain]; + + if (![StringIO read:&_mergeCommonAncestorCommitSHA1 from:is error:error]) { + break; + } + [_mergeCommonAncestorCommitSHA1 retain]; + + if (![DateIO read:&_creationDate from:is error:error]) { + break; + } + [_creationDate retain]; + if (commitVersion >= 3) { + uint64_t commitFailedFileCount = 0; + if (![IntegerIO readUInt64:&commitFailedFileCount from:is error:error]) { + break; + } + NSMutableArray *commitFailedFiles = [NSMutableArray array]; + for (uint64_t index = 0; index < commitFailedFileCount; index++) { + CommitFailedFile *cff = [[CommitFailedFile alloc] initWithInputStream:is error:error]; + if (cff == nil) { + break; + } + [commitFailedFiles addObject:cff]; + [cff release]; + } + _commitFailedFiles = [commitFailedFiles retain]; + } + ret = YES; + } while (0); + if (!ret) { + [self release]; + self = nil; + } + } + return self; +} +- (void)release { + [super release]; +} +- (void)dealloc { + [_author release]; + [_comment release]; + [_parentCommitSHA1s release]; + [_treeSHA1 release]; + [_location release]; + [_computer release]; + [_mergeCommonAncestorCommitSHA1 release]; + [_creationDate release]; + [_commitFailedFiles release]; + [super dealloc]; +} +@end + +@implementation Commit (internal) +- (BOOL)readHeader:(id )is error:(NSError **)error { + unsigned char *headerBytes = [is readExactly:HEADER_LENGTH error:error]; + if (headerBytes == NULL) { + return NO; + } + NSString *header = [[[NSString alloc] initWithBytes:headerBytes length:HEADER_LENGTH encoding:NSASCIIStringEncoding] autorelease]; + NSRange versionRange = [header rangeOfRegex:@"^CommitV(\\d{3})$" capture:1]; + commitVersion = 0; + if (versionRange.location != NSNotFound) { + NSNumberFormatter *nf = [[NSNumberFormatter alloc] init]; + NSNumber *number = [nf numberFromString:[header substringWithRange:versionRange]]; + commitVersion = [number intValue]; + [nf release]; + } + if (commitVersion > CURRENT_COMMIT_VERSION || commitVersion < 2) { + SETNSERROR(@"TreeErrorDomain", ERROR_INVALID_OBJECT_VERSION, @"invalid Commit header"); + return NO; + } + return YES; + +} +@end diff --git a/CommitFailedFile.h b/CommitFailedFile.h new file mode 100644 index 0000000..8a5977e --- /dev/null +++ b/CommitFailedFile.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@protocol BufferedInputStream; + +@interface CommitFailedFile : NSObject { + NSString *relativePath; + NSString *errorMessage; +} +- (id)initWithInputStream:(id )is error:(NSError **)error; +- (NSString *)relativePath; +- (NSString *)errorMessage; +- (void)writeTo:(NSMutableData *)data; +@end diff --git a/CommitFailedFile.m b/CommitFailedFile.m new file mode 100644 index 0000000..71fee87 --- /dev/null +++ b/CommitFailedFile.m @@ -0,0 +1,65 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "CommitFailedFile.h" +#import "StringIO.h" +#import "BufferedInputStream.h" + +@implementation CommitFailedFile +- (id)initWithInputStream:(id )is error:(NSError **)error { + if (self = [super init]) { + if (![StringIO read:&relativePath from:is error:error] + || ![StringIO read:&errorMessage from:is error:error]) { + [self release]; + self = nil; + } + [relativePath retain]; + [errorMessage retain]; + } + return self; +} +- (void)dealloc { + [relativePath release]; + [errorMessage release]; + [super dealloc]; +} +- (NSString *)relativePath { + return [[relativePath retain] autorelease]; +} +- (NSString *)errorMessage { + return [[errorMessage retain] autorelease]; +} +- (void)writeTo:(NSMutableData *)data { + [StringIO write:relativePath to:data]; + [StringIO write:errorMessage to:data]; +} +@end diff --git a/DiskPack.h b/DiskPack.h new file mode 100644 index 0000000..db6345f --- /dev/null +++ b/DiskPack.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class S3Service; +@class ServerBlob; + +@interface DiskPack : NSObject { + S3Service *s3; + NSString *s3BucketName; + NSString *computerUUID; + NSString *packSetName; + NSString *packSHA1; + NSString *s3Path; + NSString *localPath; +} ++ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; ++ (NSString *)localPathWithComputerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; +- (id)initWithS3Service:(S3Service *)theS3 s3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; +- (BOOL)makeLocal:(NSError **)error; +- (ServerBlob *)newServerBlobForObjectAtOffset:(unsigned long long)offset error:(NSError **)error; +- (BOOL)fileLength:(unsigned long long *)length error:(NSError **)error; +- (NSArray *)sortedPackIndexEntries:(NSError **)error; +@end diff --git a/DiskPack.m b/DiskPack.m new file mode 100644 index 0000000..5e93103 --- /dev/null +++ b/DiskPack.m @@ -0,0 +1,232 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#import "DiskPack.h" +#import "SetNSError.h" +#import "FDInputStream.h" +#import "StringIO.h" +#import "IntegerIO.h" +#import "PackSetSet.h" +#import "ServerBlob.h" +#import "NSFileManager_extra.h" +#import "S3Service.h" +#import "ServerBlob.h" +#import "FileInputStream.h" +#import "FileOutputStream.h" +#import "Streams.h" +#import "PackSet.h" +#import "S3ObjectReceiver.h" +#import "S3ObjectMetadata.h" +#import "PackIndexEntry.h" +#import "SHA1Hash.h" + + +@interface DiskPack (internal) +- (BOOL)savePack:(ServerBlob *)sb bytesWritten:(unsigned long long *)written error:(NSError **)error; +- (NSArray *)sortedPackIndexEntriesFromStream:(id )fis error:(NSError **)error; +@end + +@implementation DiskPack ++ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { + return [NSString stringWithFormat:@"%@/%@.pack", [PackSet s3PathWithS3BucketName:theS3BucketName computerUUID:theComputerUUID packSetName:thePackSetName], thePackSHA1]; +} ++ (NSString *)localPathWithComputerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { + return [NSString stringWithFormat:@"%@/%@/%@.pack", [PackSet localPathWithComputerUUID:theComputerUUID packSetName:thePackSetName], [thePackSHA1 substringToIndex:2], [thePackSHA1 substringFromIndex:2]]; +} +- (id)initWithS3Service:(S3Service *)theS3 s3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { + if (self = [super init]) { + s3 = [theS3 retain]; + s3BucketName = [theS3BucketName retain]; + computerUUID = [theComputerUUID retain]; + packSetName = [thePackSetName retain]; + packSHA1 = [thePackSHA1 retain]; + s3Path = [[DiskPack s3PathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain]; + localPath = [[DiskPack localPathWithComputerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain]; + } + return self; +} +- (void)dealloc { + [s3 release]; + [s3BucketName release]; + [computerUUID release]; + [packSetName release]; + [packSHA1 release]; + [s3Path release]; + [localPath release]; + [super dealloc]; +} +- (BOOL)makeLocal:(NSError **)error { + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL ret = NO; + if (![fm fileExistsAtPath:localPath]) { + HSLogDebug(@"packset %@: making pack %@ local", packSetName, packSHA1); + ServerBlob *sb = [s3 newServerBlobAtPath:s3Path error:error]; + if (sb != nil) { + unsigned long long bytesWritten; + ret = [self savePack:sb bytesWritten:&bytesWritten error:error]; + [sb release]; + } + } else { + ret = YES; + } + return ret; +} +- (ServerBlob *)newServerBlobForObjectAtOffset:(unsigned long long)offset error:(NSError **)error { + int fd = open([localPath fileSystemRepresentation], O_RDONLY); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s: %@", strerror(errno), localPath); + return nil; + } + ServerBlob *ret = nil; + FDInputStream *fdis = [[FDInputStream alloc] initWithFD:fd]; + do { + if (lseek(fd, offset, SEEK_SET) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"lseek(%@, %qu): %s", localPath, offset, strerror(errno)); + break; + } + NSString *mimeType; + NSString *downloadName; + if (![StringIO read:&mimeType from:fdis error:error] || ![StringIO read:&downloadName from:fdis error:error]) { + break; + } + uint64_t dataLen = 0; + if (![IntegerIO readUInt64:&dataLen from:fdis error:error]) { + break; + } + NSData *data = nil; + if (dataLen > 0) { + const unsigned char *bytes = [fdis readExactly:dataLen error:error]; + if (bytes == NULL) { + break; + } + data = [NSData dataWithBytes:bytes length:dataLen]; + } else { + data = [NSData data]; + } + ret = [[ServerBlob alloc] initWithData:data mimeType:mimeType downloadName:downloadName]; + } while (0); + close(fd); + [fdis release]; + return ret; +} +- (BOOL)fileLength:(unsigned long long *)length error:(NSError **)error { + struct stat st; + if (lstat([localPath fileSystemRepresentation], &st) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"lstat(%@): %s", localPath, strerror(errno)); + return NO; + } + *length = st.st_size; + return YES; +} +- (BOOL)copyToPath:(NSString *)dest error:(NSError **)error { + NSFileManager *fm = [NSFileManager defaultManager]; + if ([fm fileExistsAtPath:dest] && ![fm removeItemAtPath:dest error:error]) { + HSLogError(@"error removing old mutable pack at %@", dest); + return NO; + } + if (![fm ensureParentPathExistsForPath:dest error:error] || ![fm copyItemAtPath:localPath toPath:dest error:error]) { + HSLogError(@"error copying pack %@ to %@", localPath, dest); + return NO; + } + HSLogDebug(@"copied %@ to %@", localPath, dest); + return YES; +} +- (NSArray *)sortedPackIndexEntries:(NSError **)error { + unsigned long long length; + if (![self fileLength:&length error:error]) { + return NO; + } + FileInputStream *fis = [[FileInputStream alloc] initWithPath:localPath length:length]; + NSArray *ret = [self sortedPackIndexEntriesFromStream:fis error:error]; + [fis release]; + return ret; +} +@end + +@implementation DiskPack (internal) +- (BOOL)savePack:(ServerBlob *)sb bytesWritten:(unsigned long long *)written error:(NSError **)error { + if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:localPath error:error]) { + return NO; + } + id is = [sb newInputStream]; + NSError *myError; + BOOL ret = [Streams transferFrom:is atomicallyToFile:localPath bytesWritten:written error:&myError]; + if (ret) { + HSLogDebug(@"wrote %qu bytes to %@", *written, localPath); + } else { + if (error != NULL) { + *error = myError; + } + HSLogError(@"error making pack %@ local at %@: %@", packSHA1, localPath, [myError localizedDescription]); + } + [is release]; + return ret; +} +- (NSArray *)sortedPackIndexEntriesFromStream:(id )is error:(NSError **)error { + uint32_t packSig; + uint32_t packVersion; + if (![IntegerIO readUInt32:&packSig from:is error:error] || ![IntegerIO readUInt32:&packVersion from:is error:error]) { + return nil; + } + if (packSig != 0x5041434b) { // "PACK" + SETNSERROR(@"PackErrorDomain", -1, @"invalid pack signature"); + return nil; + } + if (packVersion != 2) { + SETNSERROR(@"PackErrorDomain", -1, @"invalid pack version"); + } + uint32_t objectCount; + if (![IntegerIO readUInt32:&objectCount from:is error:error]) { + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + for (uint32_t index = 0; index < objectCount; index++) { + uint64_t offset = [is bytesReceived]; + NSString *mimeType; + NSString *name; + uint64_t length; + if (![StringIO read:&mimeType from:is error:error] || ![StringIO read:&name from:is error:error] || ![IntegerIO readUInt64:&length from:is error:error]) { + return NO; + } + NSString *objectSHA1 = [SHA1Hash hashStream:is withlength:length error:error]; + if (objectSHA1 == nil) { + return NO; + } + PackIndexEntry *pie = [[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:length objectSHA1:objectSHA1]; + [ret addObject:pie]; + [pie release]; + } + return ret; +} +@end diff --git a/DiskPackIndex.h b/DiskPackIndex.h new file mode 100644 index 0000000..026d8bb --- /dev/null +++ b/DiskPackIndex.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class PackIndexEntry; +@class S3Service; + +@interface DiskPackIndex : NSObject { + S3Service *s3; + NSString *s3BucketName; + NSString *computerUUID; + NSString *packSetName; + NSString *packSHA1; + NSString *s3Path; + NSString *localPath; +} ++ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; ++ (NSString *)localPathWithComputerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; +- (id)initWithS3Service:(S3Service *)theS3 s3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; +- (BOOL)makeLocal:(NSError **)error; +- (NSArray *)allPackIndexEntries:(NSError **)error; +- (PackIndexEntry *)entryForSHA1:(NSString *)sha1 error:(NSError **)error; +@end diff --git a/DiskPackIndex.m b/DiskPackIndex.m new file mode 100644 index 0000000..a10e1d2 --- /dev/null +++ b/DiskPackIndex.m @@ -0,0 +1,300 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#include +#include +#import "DiskPackIndex.h" +#import "NSString_extra.h" +#import "SetNSError.h" +#import "BinarySHA1.h" +#import "PackIndexEntry.h" +#import "NSErrorCodes.h" +#import "S3Service.h" +#import "PackSetSet.h" +#import "FileOutputStream.h" +#import "Streams.h" +#import "NSFileManager_extra.h" +#import "ServerBlob.h" +#import "PackSet.h" +#import "S3ObjectReceiver.h" +#import "DiskPack.h" +#import "BlobACL.h" +#import "FileInputStreamFactory.h" +#import "ArqUserLibrary.h" +#import "Blob.h" + +typedef struct index_object { + uint64_t nbo_offset; + uint64_t nbo_datalength; + unsigned char sha1[20]; + unsigned char filler[4]; +} index_object; + +typedef struct pack_index { + uint32_t magic_number; + uint32_t nbo_version; + uint32_t nbo_fanout[256]; + index_object first_index_object; +} pack_index; + +@interface DiskPackIndex (internal) +- (BOOL)savePackIndex:(ServerBlob *)sb bytesWritten:(unsigned long long *)written error:(NSError **)error; +- (PackIndexEntry *)doEntryForSHA1:(NSString *)sha1 error:(NSError **)error; +- (PackIndexEntry *)findEntryForSHA1:(NSString *)sha1 fd:(int)fd betweenStartIndex:(uint32_t)startIndex andEndIndex:(uint32_t)endIndex error:(NSError **)error; +- (BOOL)readFanoutStartIndex:(uint32_t *)start fanoutEndIndex:(uint32_t *)end fromFD:(int)fd forSHA1FirstByte:(unsigned int)firstByte error:(NSError **)error; +@end + +@implementation DiskPackIndex ++ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { + return [NSString stringWithFormat:@"%@/%@.index", [PackSet s3PathWithS3BucketName:theS3BucketName computerUUID:theComputerUUID packSetName:thePackSetName], thePackSHA1]; +} ++ (NSString *)localPathWithComputerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { + return [NSString stringWithFormat:@"%@/%@/%@.index", [PackSet localPathWithComputerUUID:theComputerUUID packSetName:thePackSetName], [thePackSHA1 substringToIndex:2], [thePackSHA1 substringFromIndex:2]]; +} +- (id)initWithS3Service:(S3Service *)theS3 s3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { + if (self = [super init]) { + s3 = [theS3 retain]; + s3BucketName = [theS3BucketName retain]; + computerUUID = [theComputerUUID retain]; + packSetName = [thePackSetName retain]; + packSHA1 = [thePackSHA1 retain]; + s3Path = [[DiskPackIndex s3PathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain]; + localPath = [[DiskPackIndex localPathWithComputerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain]; + } + return self; +} +- (void)dealloc { + [s3 release]; + [s3BucketName release]; + [computerUUID release]; + [packSetName release]; + [packSHA1 release]; + [s3Path release]; + [localPath release]; + [super dealloc]; +} +- (BOOL)makeLocal:(NSError **)error { + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL ret = NO; + if (![fm fileExistsAtPath:localPath]) { + HSLogDebug(@"packset %@: making pack index %@ local", packSetName, packSHA1); + ServerBlob *sb = [s3 newServerBlobAtPath:s3Path error:error]; + if (sb != nil) { + unsigned long long bytesWritten; + ret = [self savePackIndex:sb bytesWritten:&bytesWritten error:error]; + [sb release]; + } else { + HSLogError(@"failed to read pack index from S3 path %@", s3Path); + } + } else { + ret = YES; + } + return ret; +} +- (NSArray *)allPackIndexEntries:(NSError **)error { + int fd = open([localPath fileSystemRepresentation], O_RDONLY); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s: %@", strerror(errno), localPath); + return nil; + } + struct stat st; + if (fstat(fd, &st) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"fstat(%@): %s", localPath, strerror(errno)); + close(fd); + return nil; + } + pack_index *the_pack_index = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (the_pack_index == MAP_FAILED) { + SETNSERROR(@"UnixErrorDomain", errno, @"mmap: %s", strerror(errno)); + close(fd); + return NO; + } + NSMutableArray *ret = [NSMutableArray array]; + uint32_t count = OSSwapBigToHostInt32(the_pack_index->nbo_fanout[255]); + index_object *indexObjects = &(the_pack_index->first_index_object); + for (uint32_t i = 0; i < count; i++) { + uint64_t offset = OSSwapBigToHostInt64(indexObjects[i].nbo_offset); + uint64_t dataLength = OSSwapBigToHostInt64(indexObjects[i].nbo_datalength); + NSString *objectSHA1 = [NSString hexStringWithBytes:indexObjects[i].sha1 length:20]; + PackIndexEntry *pie = [[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:dataLength objectSHA1:objectSHA1]; + [ret addObject:pie]; + [pie release]; + } + if (munmap(the_pack_index, st.st_size) == -1) { + HSLogError(@"munmap: %s", strerror(errno)); + } + close(fd); + return ret; +} +- (PackIndexEntry *)entryForSHA1:(NSString *)sha1 error:(NSError **)error { + if (error != NULL) { + *error = nil; + } + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + PackIndexEntry *ret = [self doEntryForSHA1:sha1 error:error]; + [ret retain]; + if (ret == nil && error != NULL) { + [*error retain]; + } + [pool drain]; + [ret autorelease]; + if (ret == nil && error != NULL) { + [*error autorelease]; + } + return ret; +} +@end + +@implementation DiskPackIndex (internal) +- (BOOL)savePackIndex:(ServerBlob *)sb bytesWritten:(unsigned long long *)written error:(NSError **)error { + if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:localPath error:error]) { + return NO; + } + id is = [sb newInputStream]; + NSError *myError = nil; + BOOL ret = [Streams transferFrom:is atomicallyToFile:localPath bytesWritten:written error:&myError]; + if (ret) { + HSLogDebug(@"wrote %qu bytes to %@", *written, localPath); + } else { + if (error != NULL) { + *error = myError; + } + HSLogError(@"error making pack %@ local at %@: %@", packSHA1, localPath, [myError localizedDescription]); + } + [is release]; + return ret; +} +- (PackIndexEntry *)doEntryForSHA1:(NSString *)sha1 error:(NSError **)error { + NSData *sha1Hex = [sha1 hexStringToData]; + unsigned char *sha1Bytes = (unsigned char *)[sha1Hex bytes]; + HSLogTrace(@"looking for sha1 %@ in packindex %@", sha1, packSHA1); + int fd = open([localPath fileSystemRepresentation], O_RDONLY); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s: %@", strerror(errno), localPath); + return nil; + } + uint32_t startIndex; + uint32_t endIndex; + if (![self readFanoutStartIndex:&startIndex fanoutEndIndex:&endIndex fromFD:fd forSHA1FirstByte:(unsigned int)sha1Bytes[0] error:error]) { + close(fd); + return nil; + } + close(fd); + if (endIndex == 0) { + SETNSERROR(@"PacksErrorDomain", ERROR_NOT_FOUND, @"sha1 %@ not found in pack", sha1); + return NO; + } + fd = open([localPath fileSystemRepresentation], O_RDONLY); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s: %@", strerror(errno), localPath); + return nil; + } + PackIndexEntry *ret = [self findEntryForSHA1:sha1 fd:fd betweenStartIndex:startIndex andEndIndex:endIndex error:error]; + close(fd); + if (ret != nil) { + HSLogTrace(@"found sha1 %@ in packindex %@", sha1, packSHA1); + } + return ret; +} +- (PackIndexEntry *)findEntryForSHA1:(NSString *)sha1 fd:(int)fd betweenStartIndex:(uint32_t)startIndex andEndIndex:(uint32_t)endIndex error:(NSError **)error { + NSData *sha1Data = [sha1 hexStringToData]; + const void *sha1Bytes = [sha1Data bytes]; + uint32_t lengthToMap = 4 + 4 + 256*4 + endIndex * sizeof(index_object); + pack_index *the_pack_index = mmap(0, lengthToMap, PROT_READ, MAP_SHARED, fd, 0); + if (the_pack_index == MAP_FAILED) { + SETNSERROR(@"UnixErrorDomain", errno, @"mmap: %s", strerror(errno)); + return NO; + } + int64_t left = startIndex; + int64_t right = endIndex - 1; + int64_t middle; + int64_t offset; + int64_t dataLength; + PackIndexEntry *pie = nil; + while (left <= right) { + middle = (left + right)/2; + index_object *firstIndexObject = &(the_pack_index->first_index_object); + index_object *middleIndexObject = &firstIndexObject[middle]; + void *middleSHA1 = middleIndexObject->sha1; + NSComparisonResult result = [BinarySHA1 compare:middleSHA1 to:sha1Bytes]; + switch (result) { + case NSOrderedAscending: + left = middle + 1; + break; + case NSOrderedDescending: + right = middle - 1; + break; + default: + offset = OSSwapBigToHostInt64(middleIndexObject->nbo_offset); + dataLength = OSSwapBigToHostInt64(middleIndexObject->nbo_datalength); + pie = [[[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:dataLength objectSHA1:sha1] autorelease]; + } + if (pie != nil) { + break; + } + } + if (munmap(the_pack_index, lengthToMap) == -1) { + HSLogError(@"munmap: %s", strerror(errno)); + } + if (pie == nil) { + SETNSERROR(@"PackErrorDomain", ERROR_NOT_FOUND, @"sha1 %@ not found in pack %@", sha1, packSHA1); + } + return pie; +} +- (BOOL)readFanoutStartIndex:(uint32_t *)start fanoutEndIndex:(uint32_t *)end fromFD:(int)fd forSHA1FirstByte:(unsigned int)firstByte error:(NSError **)error { + size_t len = 4 + 4 + 4*256; + uint32_t *map = mmap(0, len, PROT_READ, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + SETNSERROR(@"UnixErrorDomain", errno, @"mmap: %s", strerror(errno)); + return NO; + } + BOOL ret = YES; + uint32_t magicNumber = OSSwapBigToHostInt32(map[0]); + uint32_t version = OSSwapBigToHostInt32(map[1]); + if (magicNumber != 0xff744f63 || version != 2) { + SETNSERROR(@"PackErrorDomain", -1, @"invalid pack index header"); + ret = NO; + } else { + uint32_t *fanoutTable = map + 2; + *start = 0; + if (firstByte > 0) { + *start = OSSwapBigToHostInt32(fanoutTable[firstByte - 1]); + } + *end = OSSwapBigToHostInt32(fanoutTable[firstByte]); + } + if (munmap(map, len) == -1) { + HSLogError(@"munmap: %s", strerror(errno)); + } + return ret; +} +@end diff --git a/FileAttributes.h b/FileAttributes.h new file mode 100644 index 0000000..f9c5386 --- /dev/null +++ b/FileAttributes.h @@ -0,0 +1,91 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#import + +@interface FileAttributes : NSObject { + BOOL targetExists; + NSString *path; + const char *cPath; + struct stat st; + struct timespec createTime; + NSString *aclString; + int finderFlags; + int extendedFinderFlags; + NSString *finderFileType; + NSString *finderFileCreator; +} +- (id)initWithPath:(NSString *)thePath error:(NSError **)error; +- (id)initWithPath:(NSString *)thePath stat:(struct stat *)st error:(NSError **)error; + +- (unsigned long long)fileSize; +- (NSString *)aclString; +- (NSString *)aclSHA1; +- (int)uid; +- (int)gid; +- (int)mode; +- (long)mtime_sec; +- (long)mtime_nsec; +- (long)flags; +- (int)finderFlags; +- (int)extendedFinderFlags; +- (NSString *)finderFileType; +- (NSString *)finderFileCreator; +- (BOOL)isExtensionHidden; +- (BOOL)isFifo; +- (BOOL)isDevice; +- (BOOL)isSymbolicLink; +- (BOOL)isRegularFile; +- (BOOL)isSocket; +- (int)st_dev; +- (int)st_ino; +- (uint32_t)st_nlink; +- (int)st_rdev; +- (int64_t)ctime_sec; +- (int64_t)ctime_nsec; +- (int64_t)createTime_sec; +- (int64_t)createTime_nsec; +- (int64_t)st_blocks; +- (uint32_t)st_blksize; + +- (BOOL)applyFinderFileType:(NSString *)finderFileType finderFileCreator:(NSString *)finderFileCreator error:(NSError **)error; +- (BOOL)applyFlags:(int)flags error:(NSError **)error; +- (BOOL)applyAcl:(NSString *)aclString error:(NSError **)error; +- (BOOL)applyFinderFlags:(int)finderFlags error:(NSError **)error; +- (BOOL)applyExtendedFinderFlags:(int)extendedFinderFlags error:(NSError **)error; +- (BOOL)applyExtensionHidden:(BOOL)isExtensionHidden error:(NSError **)error; +- (BOOL)applyUID:(int)uid gid:(int)gid error:(NSError **)error; +- (BOOL)applyMode:(int)mode error:(NSError **)error; +- (BOOL)applyMTimeSec:(int64_t)mtime_sec mTimeNSec:(int64_t)mtime_nsec error:(NSError **)error; +- (BOOL)applyCreateTimeSec:(int64_t)createTime_sec createTimeNSec:(int64_t)createTime_sec error:(NSError **)error; +@end diff --git a/FileAttributes.m b/FileAttributes.m new file mode 100644 index 0000000..76c9ac3 --- /dev/null +++ b/FileAttributes.m @@ -0,0 +1,559 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#include +#include +#include +#include +#import "FileAttributes.h" +#import "SHA1Hash.h" +#import "FileACL.h" +#import "SetNSError.h" +#import "OSStatusDescription.h" + +#define kCouldNotCreateCFString 4 +#define kCouldNotGetStringData 5 +#define MAX_PATH 1024 + +struct createDateBuf { + u_int32_t length; + struct timespec createTime; +}; + +static OSStatus ConvertCStringToHFSUniStr(const char* cStr, HFSUniStr255 *uniStr) { + OSStatus oss = noErr; + CFStringRef tmpStringRef = CFStringCreateWithCString(kCFAllocatorDefault, cStr, kCFStringEncodingMacRoman); + if (tmpStringRef != NULL) { + if (CFStringGetCString(tmpStringRef, (char*)uniStr->unicode, sizeof(uniStr->unicode), kCFStringEncodingUnicode)) { + uniStr->length = CFStringGetLength(tmpStringRef); + } else { + oss = kCouldNotGetStringData; + } + CFRelease(tmpStringRef); + } else { + oss = kCouldNotCreateCFString; + } + return oss; +} +static OSStatus SymlinkPathMakeRef(const UInt8 *path, FSRef *ref, Boolean *isDirectory) { + FSRef tmpFSRef; + char tmpPath[MAX_PATH]; + char *tmpNamePtr; + OSStatus oss; + + strcpy(tmpPath, (char *)path); + tmpNamePtr = strrchr(tmpPath, '/'); + if (*(tmpNamePtr + 1) == '\0') { + // Last character in the path is a '/'. + *tmpNamePtr = '\0'; + tmpNamePtr = strrchr(tmpPath, '/'); + } + *tmpNamePtr = '\0'; + tmpNamePtr++; + + // Get FSRef for parent directory. + oss = FSPathMakeRef((const UInt8 *)tmpPath, &tmpFSRef, NULL); + if (oss == noErr) { + HFSUniStr255 uniName; + oss = ConvertCStringToHFSUniStr(tmpNamePtr, &uniName); + if (oss == noErr) { + FSRef newFSRef; + oss = FSMakeFSRefUnicode(&tmpFSRef, uniName.length, uniName.unicode, kTextEncodingUnknown, &newFSRef); + tmpFSRef = newFSRef; + } + } + if (oss == noErr) { + *ref = tmpFSRef; + } + return oss; +} + +@implementation FileAttributes +- (id)initWithPath:(NSString *)thePath error:(NSError **)error { + struct stat theStat; + int ret = lstat([thePath fileSystemRepresentation], &theStat); + if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s", strerror(errno)); + return nil; + } + return [self initWithPath:thePath stat:&theStat error:error]; +} +- (id)initWithPath:(NSString *)thePath stat:(struct stat *)theStat error:(NSError **)error { + if (self = [super init]) { + path = [thePath copy]; + cPath = [path fileSystemRepresentation]; + memcpy(&st, theStat, sizeof(st)); + targetExists = YES; + if ((st.st_mode & S_IFLNK) == S_IFLNK) { + struct stat targetSt; + int ret = stat(cPath, &targetSt); + if (ret == -1 && errno == ENOENT) { + targetExists = NO; + } + } + if (targetExists) { + FSRef fsRef; + Boolean isDirectory; + OSStatus oss = 0; + if ((st.st_mode & S_IFLNK) == S_IFLNK) { + oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); + } else { + oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); + } + if (oss == bdNamErr) { + HSLogInfo(@"skipping finder flags for %s: %@", cPath, [OSStatusDescription descriptionForMacOSStatus:oss]); + }else if (oss != noErr) { + SETNSERROR(@"MacFilesErrorDomain", oss, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]); + [self release]; + self = nil; + return self; + } else { + FSCatalogInfo catalogInfo; + OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoCreateDate | kFSCatInfoFinderInfo | kFSCatInfoFinderXInfo, &catalogInfo, NULL, NULL, NULL); + if (oserr) { + SETNSERROR(@"MacFilesErrorDomain", oss, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); + [self release]; + self = nil; + return self; + } + finderFlags = 0; + extendedFinderFlags = 0; + if (isDirectory) { + FolderInfo *folderInfo = (FolderInfo *)&catalogInfo.finderInfo; + finderFlags = folderInfo->finderFlags; + ExtendedFolderInfo *extFolderInfo = (ExtendedFolderInfo *)&catalogInfo.extFinderInfo; + extendedFinderFlags = extFolderInfo->extendedFinderFlags; + finderFileType = [[NSString alloc] initWithString:@""]; + finderFileCreator = [[NSString alloc] initWithString:@""]; + } else { + FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo; + finderFlags = fileInfo->finderFlags; + ExtendedFileInfo *extFileInfo = (ExtendedFileInfo *)&catalogInfo.extFinderInfo; + extendedFinderFlags = extFileInfo->extendedFinderFlags; + + char fileType[5]; + fileType[0] = *((const char *)&fileInfo->fileType + 3); + fileType[1] = *((const char *)&fileInfo->fileType + 2); + fileType[2] = *((const char *)&fileInfo->fileType + 1); + fileType[3] = *((const char *)&fileInfo->fileType); + fileType[4] = 0; + finderFileType = [[NSString alloc] initWithCString:fileType encoding:NSUTF8StringEncoding]; + char fileCreator[5]; + fileCreator[0] = *((const char *)&fileInfo->fileCreator + 3); + fileCreator[1] = *((const char *)&fileInfo->fileCreator + 2); + fileCreator[2] = *((const char *)&fileInfo->fileCreator + 1); + fileCreator[3] = *((const char *)&fileInfo->fileCreator); + fileCreator[4] = 0; + finderFileCreator = [[NSString alloc] initWithCString:fileCreator encoding:NSUTF8StringEncoding]; + } + } + } + + if (![FileACL aclText:&aclString forFile:path error:error]) { + [self release]; + self = nil; + return self; + } + [aclString retain]; + + struct attrlist attrList; + memset(&attrList, 0, sizeof(attrList)); + attrList.bitmapcount = ATTR_BIT_MAP_COUNT; + attrList.commonattr = ATTR_CMN_CRTIME; + struct createDateBuf createDateBuf; + if (getattrlist(cPath, &attrList, &createDateBuf, sizeof(createDateBuf), FSOPT_NOFOLLOW) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"Error getting create date for %@: %s", thePath, strerror(errno)); + [self release]; + self = nil; + return self; + } + createTime.tv_sec = createDateBuf.createTime.tv_sec; + createTime.tv_nsec = createDateBuf.createTime.tv_nsec; + } + return self; +} +- (void)dealloc { + [path release]; + [aclString release]; + [finderFileType release]; + [finderFileCreator release]; + [super dealloc]; +} +- (unsigned long long)fileSize { + return (unsigned long long)st.st_size; +} +- (NSString *)aclString { + return aclString; +} +- (NSString *)aclSHA1 { + NSString *sha1 = nil; + if (aclString) { + sha1 = [SHA1Hash hashData:[aclString dataUsingEncoding:NSUTF8StringEncoding]]; + } + return sha1; +} +- (int)uid { + return st.st_uid; +} +- (int)gid { + return st.st_gid; +} +- (int)mode { + return st.st_mode; +} +- (long)mtime_sec { + return st.st_mtimespec.tv_sec; +} +- (long)mtime_nsec { + return st.st_mtimespec.tv_nsec; +} +- (long)flags { + return st.st_flags; +} + +- (int)finderFlags { + return finderFlags; +} +- (int)extendedFinderFlags { + return extendedFinderFlags; +} +- (NSString *)finderFileType { + return finderFileType; +} +- (NSString *)finderFileCreator { + return finderFileCreator; +} +- (BOOL)isExtensionHidden { + return (st.st_flags & UF_HIDDEN) == UF_HIDDEN; +} +- (BOOL)isFifo { + return (st.st_mode & S_IFIFO) == S_IFIFO; +} +- (BOOL)isDevice { + return (st.st_mode & S_IFCHR) == S_IFCHR || (st.st_mode & S_IFBLK) == S_IFBLK; +} +- (BOOL)isSymbolicLink { + return (st.st_mode & S_IFLNK) == S_IFLNK; +} +- (BOOL)isRegularFile { + return (st.st_mode & S_IFREG) == S_IFREG; +} +- (BOOL)isSocket { + return (st.st_mode & S_IFSOCK) == S_IFSOCK; +} +- (int)st_dev { + return st.st_dev; +} +- (int)st_ino { + return st.st_ino; +} +- (uint32_t)st_nlink { + return st.st_nlink; +} +- (int)st_rdev { + return st.st_rdev; +} +- (int64_t)ctime_sec { + return st.st_ctimespec.tv_sec; +} +- (int64_t)ctime_nsec { + return st.st_ctimespec.tv_nsec; +} +- (int64_t)createTime_sec { + return createTime.tv_sec; +} +- (int64_t)createTime_nsec { + return createTime.tv_nsec; +} +- (int64_t)st_blocks { + return st.st_blocks; +} +- (uint32_t)st_blksize { + return st.st_blksize; +} +- (BOOL)applyFinderFileType:(NSString *)fft finderFileCreator:(NSString *)ffc error:(NSError **)error { + if (targetExists && (![fft isEqualToString:finderFileType] || ![ffc isEqualToString:finderFileCreator])) { + if ([fft length] != 4) { + HSLogWarn(@"not applying finder file type '%@' to %@: invalid length (must be 4 characters)", fft, path); + } else if ([ffc length] != 4) { + HSLogWarn(@"not applying finder file type '%@' to %@: invalid length (must be 4 characters)", ffc, path); + } else { + FSRef fsRef; + Boolean isDirectory; + OSStatus oss = 0; + if ((st.st_mode & S_IFLNK) == S_IFLNK) { + oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); + } else { + oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); + } + if (oss != noErr) { + if (oss == bdNamErr) { + HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath); + return YES; + } else { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]); + return NO; + } + } + if (isDirectory) { + SETNSERROR(@"FileManagerErrorDomain", -1, @"cannot apply finderFileType to a directory"); + return NO; + } + + FSCatalogInfo catalogInfo; + OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL); + if (oserr) { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); + return NO; + } + FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo; + const char *fileType = [fft UTF8String]; + char *destFileType = (char *)&fileInfo->fileType; + destFileType[3] = fileType[0]; + destFileType[2] = fileType[1]; + destFileType[1] = fileType[2]; + destFileType[0] = fileType[3]; + + const char *fileCreator = [ffc UTF8String]; + char *destFileCreator = (char *)&fileInfo->fileCreator; + destFileCreator[3] = fileCreator[0]; + destFileCreator[2] = fileCreator[1]; + destFileCreator[1] = fileCreator[2]; + destFileCreator[0] = fileCreator[3]; + + oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo); + if (oserr) { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); + return NO; + } + [finderFileType release]; + finderFileType = [fft copy]; + [finderFileCreator release]; + finderFileCreator = [ffc copy]; + } + } + return YES; +} +- (BOOL)applyFlags:(int)flags error:(NSError **)error { + if (targetExists && flags != st.st_flags) { + HSLogTrace(@"chflags(%s, %d)", cPath, flags); + if (chflags(cPath, flags) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"chflags: %s", strerror(errno)); + return NO; + } + st.st_flags = flags; + } + return YES; +} +- (BOOL)applyAcl:(NSString *)theACLString error:(NSError **)error { + BOOL ret = YES; + if (![theACLString isEqualToString:aclString]) { + ret = [FileACL writeACLText:theACLString toFile:path error:error]; + if (ret) { + [aclString release]; + aclString = [theACLString retain]; + } + } + return ret; +} +- (BOOL)applyFinderFlags:(int)ff error:(NSError **)error { + if (targetExists && ff != finderFlags) { + FSRef fsRef; + Boolean isDirectory; + OSStatus oss = 0; + if ((st.st_mode & S_IFLNK) == S_IFLNK) { + oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); + } else { + oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); + } + if (oss != noErr) { + if (oss == bdNamErr) { + HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath); + return YES; + } else { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]); + return NO; + } + } + FSCatalogInfo catalogInfo; + OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL); + if (oserr) { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); + return NO; + } + if (isDirectory) { + FolderInfo *folderInfo = (FolderInfo *)&catalogInfo.finderInfo; + folderInfo->finderFlags = ff; + } else { + FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo; + fileInfo->finderFlags = ff; + } + oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo); + if (oserr) { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); + return NO; + } + finderFlags = ff; + } + return YES; +} +- (BOOL)applyExtendedFinderFlags:(int)eff error:(NSError **)error { + if (targetExists && extendedFinderFlags != eff) { + FSRef fsRef; + Boolean isDirectory; + OSStatus oss = 0; + if ((st.st_mode & S_IFLNK) == S_IFLNK) { + oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); + } else { + oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); + } + if (oss != noErr) { + if (oss == bdNamErr) { + HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath); + return YES; + } else { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]); + return NO; + } + } + FSCatalogInfo catalogInfo; + OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderXInfo, &catalogInfo, NULL, NULL, NULL); + if (oserr) { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); + return NO; + } + if (isDirectory) { + ExtendedFolderInfo *extFolderInfo = (ExtendedFolderInfo *)&catalogInfo.extFinderInfo; + extFolderInfo->extendedFinderFlags = eff; + } else { + ExtendedFileInfo *extFileInfo = (ExtendedFileInfo *)&catalogInfo.extFinderInfo; + extFileInfo->extendedFinderFlags = eff; + } + oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderXInfo, &catalogInfo); + if (oserr) { + SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); + return NO; + } + extendedFinderFlags = eff; + } + return YES; +} +- (BOOL)applyExtensionHidden:(BOOL)hidden error:(NSError **)error { + BOOL ret = YES; + if (hidden != [self isExtensionHidden]) { + NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:hidden], NSFileExtensionHidden, nil]; + ret = [[NSFileManager defaultManager] setAttributes:attribs ofItemAtPath:path error:error]; + if (ret) { + if (hidden) { + st.st_flags = st.st_flags & UF_HIDDEN; + } else { + st.st_flags = st.st_flags & (0xffffffff ^ UF_HIDDEN); + } + } + } + return ret; +} +- (BOOL)applyUID:(int)uid gid:(int)gid error:(NSError **)error { + if (uid != st.st_uid || gid != st.st_gid) { + HSLogTrace(@"lchown(%s, %d, %d)", cPath, uid, gid); + if (lchown(cPath, uid, gid) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"lchown: %s", strerror(errno)); + return NO; + } + st.st_uid = uid; + st.st_gid = gid; + } + return YES; +} +- (BOOL)applyMode:(int)mode error:(NSError **)error { + if (mode != st.st_mode) { + if ((st.st_mode & S_IFDIR) == S_IFDIR) { + HSLogTrace(@"chmod(%s, %d)", cPath, mode); + int ret = chmod(cPath, mode); + if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"Setting permissions on %@: %s", path, strerror(errno)); + return NO; + } + } else { + int fd = open(cPath, O_RDWR|O_SYMLINK); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s", strerror(errno)); + return NO; + } + HSLogTrace(@"fchmod symlink (%s, %d)", cPath, mode); + int ret = fchmod(fd, mode); + close(fd); + if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"Setting permissions on %@: %s", path, strerror(errno)); + return NO; + } + } + st.st_mode = mode; + } + return YES; +} +- (BOOL)applyMTimeSec:(int64_t)mtime_sec mTimeNSec:(int64_t)mtime_nsec error:(NSError **)error { + if (st.st_mtimespec.tv_sec != mtime_sec + || st.st_mtimespec.tv_nsec != mtime_nsec) { + struct timespec mtimeSpec = { mtime_sec, mtime_nsec }; + struct timeval atimeVal; + struct timeval mtimeVal; + TIMESPEC_TO_TIMEVAL(&atimeVal, &mtimeSpec); // Just use mtime because we don't have atime, nor do we care about atime. + TIMESPEC_TO_TIMEVAL(&mtimeVal, &mtimeSpec); + struct timeval timevals[2]; + timevals[0] = atimeVal; + timevals[1] = mtimeVal; + if (utimes(cPath, timevals) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"utimes(%@): %s", path, strerror(errno)); + return NO; + } + } + return YES; +} +- (BOOL)applyCreateTimeSec:(int64_t)theCreateTime_sec createTimeNSec:(int64_t)theCreateTime_nsec error:(NSError **)error { + if (createTime.tv_sec != theCreateTime_sec || createTime.tv_nsec != theCreateTime_nsec) { + createTime.tv_sec = theCreateTime_sec; + createTime.tv_nsec = theCreateTime_nsec; + + struct attrlist attrList; + memset(&attrList, 0, sizeof(attrList)); + attrList.bitmapcount = ATTR_BIT_MAP_COUNT; + attrList.commonattr = ATTR_CMN_CRTIME; + if (setattrlist(cPath, &attrList, &createTime, sizeof(createTime), FSOPT_NOFOLLOW) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"Error setting create date on %@: %s", path, strerror(errno)); + return NO; + } + } + return YES; +} +@end diff --git a/HSLog.h b/HSLog.h new file mode 100644 index 0000000..0297b8f --- /dev/null +++ b/HSLog.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include + +extern unsigned int global_hslog_level; + +extern void setHSLogLevel(int level); +extern int hsLogLevelForName(NSString *levelName); +extern NSString *nameForHSLogLevel(int level); +#define HSLOG_LEVEL_TRACE (5) +#define HSLOG_LEVEL_DEBUG (4) +#define HSLOG_LEVEL_INFO (3) +#define HSLOG_LEVEL_WARN (2) +#define HSLOG_LEVEL_ERROR (1) +#define HSLOG_LEVEL_NONE (0) + +#define HSLogTrace( s, ... ) { if (global_hslog_level >= HSLOG_LEVEL_TRACE) { NSLog(@"TRACE [thread %x] %p %@:%d %@", pthread_mach_thread_np(pthread_self()), self, [@__FILE__ lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__]); } } +#define HSLogDebug( s, ... ) { if (global_hslog_level >= HSLOG_LEVEL_DEBUG) { NSLog(@"DEBUG [thread %x] %p %@:%d %@", pthread_mach_thread_np(pthread_self()), self, [@__FILE__ lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__]); } } +#define HSLogDebugStatic( s, ... ) { if (global_hslog_level >= HSLOG_LEVEL_DEBUG) { NSLog(@"DEBUG [thread %x] %@:%d %@", pthread_mach_thread_np(pthread_self()), [@__FILE__ lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__]); } } +#define HSLogInfo( s, ... ) { if (global_hslog_level >= HSLOG_LEVEL_INFO) { NSLog(@"INFO [thread %x] %@", pthread_mach_thread_np(pthread_self()), [NSString stringWithFormat:(s), ##__VA_ARGS__]); } } +#define HSLogWarn( s, ... ) { if (global_hslog_level >= HSLOG_LEVEL_WARN) { NSLog(@"WARN [thread %x] %@", pthread_mach_thread_np(pthread_self()), [NSString stringWithFormat:(s), ##__VA_ARGS__]); } } +#define HSLogError( s, ... ) { if (global_hslog_level >= HSLOG_LEVEL_ERROR) { NSLog(@"ERROR [thread %x] %@", pthread_mach_thread_np(pthread_self()), [NSString stringWithFormat:(s), ##__VA_ARGS__]); } } +#define HSLog( s, ... ) NSLog(@"%@", [NSString stringWithFormat:(s), ##__VA_ARGS__]) diff --git a/HSLog.m b/HSLog.m new file mode 100644 index 0000000..6af1d36 --- /dev/null +++ b/HSLog.m @@ -0,0 +1,68 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "HSLog.h" + +unsigned int global_hslog_level = HSLOG_LEVEL_WARN; + +void setHSLogLevel(int level) { + global_hslog_level = level; +} +extern int hsLogLevelForName(NSString *levelName) { + if ([[levelName lowercaseString] isEqualToString:@"error"]) { + return HSLOG_LEVEL_ERROR; + } else if ([[levelName lowercaseString] isEqualToString:@"warn"]) { + return HSLOG_LEVEL_WARN; + } else if ([[levelName lowercaseString] isEqualToString:@"info"]) { + return HSLOG_LEVEL_INFO; + } else if ([[levelName lowercaseString] isEqualToString:@"debug"]) { + return HSLOG_LEVEL_DEBUG; + } else if ([[levelName lowercaseString] isEqualToString:@"trace"]) { + return HSLOG_LEVEL_TRACE; + } + return HSLOG_LEVEL_NONE; +} +extern NSString *nameForHSLogLevel(int level) { + switch (level) { + case HSLOG_LEVEL_ERROR: + return @"Error"; + case HSLOG_LEVEL_WARN: + return @"Warn"; + case HSLOG_LEVEL_INFO: + return @"Info"; + case HSLOG_LEVEL_DEBUG: + return @"Debug"; + case HSLOG_LEVEL_TRACE: + return @"Trace"; + } + return @"none"; +} \ No newline at end of file diff --git a/Node.h b/Node.h new file mode 100644 index 0000000..e5883e7 --- /dev/null +++ b/Node.h @@ -0,0 +1,104 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class MutableS3Repo; +#import "InputStream.h" +#import "InputStreamFactory.h" +#import "OutputStream.h" + +@interface Node : NSObject { + int treeVersion; + BOOL isTree; + unsigned long long dataSize; + NSMutableArray *dataSHA1s; + NSString *thumbnailSHA1; + NSString *previewSHA1; + NSString *xattrsSHA1; + unsigned long long xattrsSize; + NSString *aclSHA1; + int uid; + int gid; + int mode; + int64_t mtime_sec; + int64_t mtime_nsec; + long long flags; + int finderFlags; + int extendedFinderFlags; + NSString *finderFileType; + NSString *finderFileCreator; + BOOL isFileExtensionHidden; + int st_dev; + int st_ino; + uint32_t st_nlink; // in struct stat, it's only 16 bits. + int st_rdev; + int64_t ctime_sec; + int64_t ctime_nsec; + int64_t createTime_sec; + int64_t createTime_nsec; + int64_t st_blocks; + uint32_t st_blksize; +} +- (id)initWithInputStream:(id )is treeVersion:(int)theTreeVersion error:(NSError **)error; +- (void)writeToData:(NSMutableData *)data; +- (BOOL)dataMatchesStatData:(struct stat *)st; + +@property(readonly) BOOL isTree; +@property(readonly,copy) NSString *treeSHA1; +@property(readonly,copy) NSArray *dataSHA1s; + +@property(readonly) unsigned long long dataSize; +@property(readonly,copy) NSString *thumbnailSHA1; +@property(readonly,copy) NSString *previewSHA1; +@property(readonly,copy) NSString *xattrsSHA1; +@property(readonly) unsigned long long xattrsSize; +@property(readonly,copy) NSString *aclSHA1; +@property(readonly) int uid; +@property(readonly) int gid; +@property(readonly) int mode; +@property(readonly) long long mtime_sec; +@property(readonly) long long mtime_nsec; +@property(readonly) long long flags; +@property(readonly) int finderFlags; +@property(readonly) int extendedFinderFlags; +@property(readonly,copy) NSString *finderFileType; +@property(readonly,copy) NSString *finderFileCreator; +@property(readonly) BOOL isFileExtensionHidden; +@property(readonly) int treeVersion; +@property(readonly) int st_rdev; +@property(readonly) long long ctime_sec; +@property(readonly) long long ctime_nsec; +@property(readonly) long long createTime_sec; +@property(readonly) long long createTime_nsec; +@property(readonly) uint32_t st_nlink; +@property(readonly) int st_ino; +@end diff --git a/Node.m b/Node.m new file mode 100644 index 0000000..982fd1f --- /dev/null +++ b/Node.m @@ -0,0 +1,165 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#import "BooleanIO.h" +#import "IntegerIO.h" +#import "StringIO.h" +#import "Node.h" +#import "InputStream.h" +#import "Blob.h" +#import "SetNSError.h" +#import "Tree.h" + +@implementation Node +@synthesize isTree, dataSize, thumbnailSHA1, previewSHA1, xattrsSHA1, xattrsSize, aclSHA1, uid, gid, mode, mtime_sec, mtime_nsec, flags, finderFlags, extendedFinderFlags, finderFileType, finderFileCreator, isFileExtensionHidden, treeVersion, st_rdev; +@synthesize ctime_sec, ctime_nsec, createTime_sec, createTime_nsec, st_nlink, st_ino; +@dynamic treeSHA1, dataSHA1s; + +- (id)initWithInputStream:(id )is treeVersion:(int)theTreeVersion error:(NSError **)error { + if (self = [super init]) { + treeVersion = theTreeVersion; + dataSHA1s = [[NSMutableArray alloc] init]; + BOOL ret = NO; + do { + if (![BooleanIO read:&isTree from:is error:error]) { + break; + } + int dataSHA1sCount; + if (![IntegerIO readInt32:&dataSHA1sCount from:is error:error]) { + break; + } + for (int i = 0; i < dataSHA1sCount; i++) { + NSString *dataSHA1; + if (![StringIO read:&dataSHA1 from:is error:error]) { + break; + } + [dataSHA1s addObject:dataSHA1]; + } + ret = [IntegerIO readUInt64:&dataSize from:is error:error] + && [StringIO read:&thumbnailSHA1 from:is error:error] + && [StringIO read:&previewSHA1 from:is error:error] + && [StringIO read:&xattrsSHA1 from:is error:error] + && [IntegerIO readUInt64:&xattrsSize from:is error:error] + && [StringIO read:&aclSHA1 from:is error:error] + && [IntegerIO readInt32:&uid from:is error:error] + && [IntegerIO readInt32:&gid from:is error:error] + && [IntegerIO readInt32:&mode from:is error:error] + && [IntegerIO readInt64:&mtime_sec from:is error:error] + && [IntegerIO readInt64:&mtime_nsec from:is error:error] + && [IntegerIO readInt64:&flags from:is error:error] + && [IntegerIO readInt32:&finderFlags from:is error:error] + && [IntegerIO readInt32:&extendedFinderFlags from:is error:error] + && [StringIO read:&finderFileType from:is error:error] + && [StringIO read:&finderFileCreator from:is error:error] + && [BooleanIO read:&isFileExtensionHidden from:is error:error] + && [IntegerIO readInt32:&st_dev from:is error:error] + && [IntegerIO readInt32:&st_ino from:is error:error] + && [IntegerIO readUInt32:&st_nlink from:is error:error] + && [IntegerIO readInt32:&st_rdev from:is error:error] + && [IntegerIO readInt64:&ctime_sec from:is error:error] + && [IntegerIO readInt64:&ctime_nsec from:is error:error] + && [IntegerIO readInt64:&createTime_sec from:is error:error] + && [IntegerIO readInt64:&createTime_nsec from:is error:error] + && [IntegerIO readInt64:&st_blocks from:is error:error] + && [IntegerIO readUInt32:&st_blksize from:is error:error]; + [thumbnailSHA1 retain]; + [previewSHA1 retain]; + [xattrsSHA1 retain]; + [aclSHA1 retain]; + [finderFileType retain]; + [finderFileCreator retain]; + } while(0); + if (!ret) { + [self release]; + self = nil; + return nil; + } + } + return self; +} +- (void)dealloc { + [dataSHA1s release]; + [thumbnailSHA1 release]; + [previewSHA1 release]; + [xattrsSHA1 release]; + [aclSHA1 release]; + [finderFileType release]; + [finderFileCreator release]; + [super dealloc]; +} + +- (void)writeToData:(NSMutableData *)data { + [BooleanIO write:isTree to:data]; + [IntegerIO writeInt32:(int32_t)[dataSHA1s count] to:data]; + for (NSString *dataSHA1 in dataSHA1s) { + [StringIO write:dataSHA1 to:data]; + } + [IntegerIO writeUInt64:dataSize to:data]; + [StringIO write:thumbnailSHA1 to:data]; + [StringIO write:previewSHA1 to:data]; + [StringIO write:xattrsSHA1 to:data]; + [IntegerIO writeUInt64:xattrsSize to:data]; + [StringIO write:aclSHA1 to:data]; + [IntegerIO writeInt32:uid to:data]; + [IntegerIO writeInt32:gid to:data]; + [IntegerIO writeInt32:mode to:data]; + [IntegerIO writeInt64:mtime_sec to:data]; + [IntegerIO writeInt64:mtime_nsec to:data]; + [IntegerIO writeInt64:flags to:data]; + [IntegerIO writeInt32:finderFlags to:data]; + [IntegerIO writeInt32:extendedFinderFlags to:data]; + [StringIO write:finderFileType to:data]; + [StringIO write:finderFileCreator to:data]; + [BooleanIO write:isFileExtensionHidden to:data]; + [IntegerIO writeInt32:st_dev to:data]; + [IntegerIO writeInt32:st_ino to:data]; + [IntegerIO writeUInt32:st_nlink to:data]; + [IntegerIO writeInt32:st_rdev to:data]; + [IntegerIO writeInt64:ctime_sec to:data]; + [IntegerIO writeInt64:ctime_nsec to:data]; + [IntegerIO writeInt64:createTime_sec to:data]; + [IntegerIO writeInt64:createTime_nsec to:data]; + [IntegerIO writeInt64:st_blocks to:data]; + [IntegerIO writeUInt32:st_blksize to:data]; +} +- (BOOL)dataMatchesStatData:(struct stat *)st { + return (st->st_mtimespec.tv_sec == mtime_sec && st->st_mtimespec.tv_nsec == mtime_nsec & st->st_size == dataSize); +} +- (NSString *)treeSHA1 { + NSAssert(isTree, @"must be a Tree"); + return [dataSHA1s objectAtIndex:0]; +} +- (NSArray *)dataSHA1s { + return dataSHA1s; +} +@end diff --git a/PackIndexEntry.h b/PackIndexEntry.h new file mode 100644 index 0000000..6822a4a --- /dev/null +++ b/PackIndexEntry.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface PackIndexEntry : NSObject { + NSString *packSHA1; + unsigned long long offset; + unsigned long long dataLength; + NSString *objectSHA1; +} +- (id)initWithPackSHA1:(NSString *)thePackSHA1 offset:(unsigned long long)theOffset dataLength:(unsigned long long)theDataLength objectSHA1:(NSString *)theObjectSHA1; +- (NSString *)packSHA1; +- (unsigned long long)offset; +- (unsigned long long)dataLength; +- (NSString *)objectSHA1; +@end diff --git a/PackIndexEntry.m b/PackIndexEntry.m new file mode 100644 index 0000000..6e1fc74 --- /dev/null +++ b/PackIndexEntry.m @@ -0,0 +1,63 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PackIndexEntry.h" + + +@implementation PackIndexEntry +- (id)initWithPackSHA1:(NSString *)thePackSHA1 offset:(unsigned long long)theOffset dataLength:(unsigned long long)theDataLength objectSHA1:(NSString *)theObjectSHA1 { + if (self = [super init]) { + packSHA1 = [thePackSHA1 copy]; + offset = theOffset; + dataLength = theDataLength; + objectSHA1 = [theObjectSHA1 copy]; + } + return self; +} +- (void)dealloc { + [packSHA1 release]; + [objectSHA1 release]; + [super dealloc]; +} +- (NSString *)packSHA1 { + return packSHA1; +} +- (unsigned long long)offset { + return offset; +} +- (unsigned long long)dataLength { + return dataLength; +} +- (NSString *)objectSHA1 { + return objectSHA1; +} +@end diff --git a/PackSet.h b/PackSet.h new file mode 100644 index 0000000..652e974 --- /dev/null +++ b/PackSet.h @@ -0,0 +1,66 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class Blob; +@class S3Service; +@class ServerBlob; + +@interface PackSet : NSObject { + NSString *packSetName; + NSString *escapedPackSetName; + NSString *packSetDir; + S3Service *s3; + NSString *s3BucketName; + NSString *computerUUID; + BOOL keepPacksLocal; + NSMutableSet *packSHA1s; + NSString *currentPackSHA1; + NSMutableDictionary *packIndexEntries; +} ++ (NSString *)errorDomain; ++ (unsigned long long)maxPackFileSizeMB; ++ (unsigned long long)maxPackItemSizeBytes; ++ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName; ++ (NSString *)localPathWithComputerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName; + +- (id)initWithName:(NSString *)thePackSetName + s3Service:(S3Service *)theS3 + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID + keepPacksLocal:(BOOL)isKeepPacksLocal + packSHA1s:(NSArray *)thePackSHA1s + error:(NSError **)error; +- (NSString *)name; +- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error; +- (BOOL)containsBlobForSHA1:(NSString *)sha1; +@end diff --git a/PackSet.m b/PackSet.m new file mode 100644 index 0000000..720ec08 --- /dev/null +++ b/PackSet.m @@ -0,0 +1,200 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PackSet.h" +#import "S3Service.h" +#import "SHA1Hash.h" +#import "S3ObjectReceiver.h" +#import "SetNSError.h" +#import "DataInputStream.h" +#import "PackSetSet.h" +#import "NSData-InputStream.h" +#import "ServerBlob.h" +#import "NSErrorCodes.h" +#import "DiskPackIndex.h" +#import "PackIndexEntry.h" +#import "DiskPack.h" +#import "RegexKitLite.h" +#import "HTTP.h" + +static unsigned long long DEFAULT_MAX_PACK_FILE_SIZE_MB = 10; +static unsigned long long DEFAULT_MAX_PACK_ITEM_SIZE_BYTES = 65536; +static double DEFAULT_MAX_REUSABLE_PACK_FILE_SIZE_FRACTION = 0.6; + + + +@interface PackSet (internal) ++ (unsigned long long)maxReusablePackFileSizeBytes; +- (BOOL)loadPackIndexEntries:(NSString *)packSHA1 totalDataSize:(unsigned long long *)totalDataSize error:(NSError **)error; +- (PackIndexEntry *)packIndexEntryForObjectSHA1:(NSString *)objectSHA1 error:(NSError **)error; +- (PackIndexEntry *)packIndexEntryForObjectSHA1:(NSString *)objectSHA1 inPackSHA1:(NSString *)packSHA1 error:(NSError **)error; +@end + +@implementation PackSet ++ (NSString *)errorDomain { + return @"PackSetErrorDomain"; +} ++ (unsigned long long)maxPackFileSizeMB { + return DEFAULT_MAX_PACK_FILE_SIZE_MB; +} ++ (unsigned long long)maxPackItemSizeBytes { + return DEFAULT_MAX_PACK_ITEM_SIZE_BYTES; +} ++ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName { + return [NSString stringWithFormat:@"%@/%@", [PackSetSet s3PathWithS3BucketName:theS3BucketName computerUUID:theComputerUUID], [thePackSetName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; +} ++ (NSString *)localPathWithComputerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName { + return [NSString stringWithFormat:@"%@/%@", [PackSetSet localPathWithComputerUUID:theComputerUUID], [thePackSetName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; +} +- (id)initWithName:(NSString *)thePackSetName + s3Service:(S3Service *)theS3 + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID + keepPacksLocal:(BOOL)isKeepPacksLocal + packSHA1s:(NSArray *)thePackSHA1s + error:(NSError **)error { + if (self = [super init]) { + packSetName = [thePackSetName copy]; + s3 = [theS3 retain]; + s3BucketName = [theS3BucketName copy]; + computerUUID = [theComputerUUID copy]; + keepPacksLocal = isKeepPacksLocal; + escapedPackSetName = [[thePackSetName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] copy]; + packSetDir = [[PackSet localPathWithComputerUUID:theComputerUUID packSetName:packSetName] retain]; + packSHA1s = [[NSMutableSet alloc] initWithArray:thePackSHA1s]; + packIndexEntries = [[NSMutableDictionary alloc] init]; + for (NSString *packSHA1 in packSHA1s) { + unsigned long long totalDataSize = 0; + if (![self loadPackIndexEntries:packSHA1 totalDataSize:&totalDataSize error:error]) { + [self release]; + return nil; + } + if (totalDataSize < [PackSet maxReusablePackFileSizeBytes] && currentPackSHA1 == nil) { + currentPackSHA1 = [packSHA1 copy]; + } + } + } + return self; +} +- (void)dealloc { + [packSetName release]; + [escapedPackSetName release]; + [packSetDir release]; + [s3 release]; + [s3BucketName release]; + [computerUUID release]; + [packSHA1s release]; + [packIndexEntries release]; + [currentPackSHA1 release]; + [super dealloc]; +} +- (NSString *)name { + return packSetName; +} +- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error { + HSLogTrace(@"packset %@ looking for SHA1 %@", packSetName, sha1); + NSError *myError = nil; + PackIndexEntry *entry = [self packIndexEntryForObjectSHA1:sha1 error:&myError]; + if (entry == nil && [myError code] != ERROR_NOT_FOUND) { + if (error != NULL) { + *error = myError; + } + HSLogError(@"error reading pack index entry for %@ from pack set %@: %@", sha1, packSetName, [myError localizedDescription]); + return nil; + } + if (entry != nil) { + NSError *myError = nil; + DiskPack *diskPack = [[DiskPack alloc] initWithS3Service:s3 s3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:[entry packSHA1]]; + if (![diskPack makeLocal:&myError]) { + [diskPack release]; + if ([[myError domain] isEqualToString:[S3Service errorDomain]] && [myError code] == HTTP_NOT_FOUND) { + SETNSERROR(@"PackSetErrorDomain", ERROR_NOT_FOUND, @"pack %@ not found in S3: %@", [entry packSHA1], [myError localizedDescription]); + } else if (error != NULL) { + *error = myError; + } + return nil; + } + ServerBlob *sb = [diskPack newServerBlobForObjectAtOffset:[entry offset] error:error]; + [diskPack release]; + return sb; + } + SETNSERROR(@"PackErrorDomain", ERROR_NOT_FOUND, @"sha1 %@ not found", sha1); + return nil; +} +- (BOOL)containsBlobForSHA1:(NSString *)sha1 { + return [packIndexEntries objectForKey:sha1] != nil; +} +@end + +@implementation PackSet (internal) ++ (unsigned long long)maxReusablePackFileSizeBytes { + return (unsigned long long)((double)([PackSet maxPackFileSizeMB] * 1000000) * DEFAULT_MAX_REUSABLE_PACK_FILE_SIZE_FRACTION); +} +- (PackIndexEntry *)packIndexEntryForObjectSHA1:(NSString *)objectSHA1 error:(NSError **)error { + PackIndexEntry *pie = [packIndexEntries objectForKey:objectSHA1]; + if (pie == nil) { + SETNSERROR(@"PackErrorDomain", ERROR_NOT_FOUND, @"pie not found for %@", objectSHA1); + } + return pie; +} +- (PackIndexEntry *)packIndexEntryForObjectSHA1:(NSString *)objectSHA1 inPackSHA1:(NSString *)packSHA1 error:(NSError **)error { + PackIndexEntry *pie = [packIndexEntries objectForKey:objectSHA1]; + if (pie == nil) { + SETNSERROR(@"PackErrorDomain", ERROR_NOT_FOUND, @"pie not found for %@", objectSHA1); + } + return pie; +} +- (BOOL)loadPackIndexEntries:(NSString *)packSHA1 totalDataSize:(unsigned long long *)totalDataSize error:(NSError **)error { + *totalDataSize = 0; + DiskPackIndex *index = [[DiskPackIndex alloc] initWithS3Service:s3 s3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1]; + BOOL ret = NO; + do { + if (![index makeLocal:error]) { + break; + } + NSArray *pies = [index allPackIndexEntries:error]; + if (pies == nil) { + break; + } + for (PackIndexEntry *pie in pies) { + [packIndexEntries setObject:pie forKey:[pie objectSHA1]]; + unsigned long long dataEndOffset = [pie offset] + [pie dataLength]; + if (dataEndOffset > *totalDataSize) { + *totalDataSize = dataEndOffset; + } + } + ret = YES; + } while (0); + [index release]; + return ret; +} +@end diff --git a/PackSetSet.h b/PackSetSet.h new file mode 100644 index 0000000..6b99946 --- /dev/null +++ b/PackSetSet.h @@ -0,0 +1,54 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class ServerBlob; +@class S3Service; +@class Blob; + +@interface PackSetSet : NSObject { + S3Service *s3; + NSString *s3BucketName; + NSString *computerUUID; + NSMutableDictionary *packSets; +} ++ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID; ++ (NSString *)localPathWithComputerUUID:(NSString *)computerUUID; +- (id)initWithS3Service:(S3Service *)theS3 + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID; +- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName error:(NSError **)error; +- (BOOL)containsBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName; + +// Sync local cache files to S3 data; reload PackIndexEntries from local cache files. +- (BOOL)resetFromS3:(NSError **)error; +@end diff --git a/PackSetSet.m b/PackSetSet.m new file mode 100644 index 0000000..0617704 --- /dev/null +++ b/PackSetSet.m @@ -0,0 +1,224 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PackSetSet.h" +#import "PackSet.h" +#import "SetNSError.h" +#import "NSErrorCodes.h" +#import "S3Service.h" +#import "ArqUserLibrary.h" +#import "RegexKitLite.h" + +@interface PackSetSet (internal) +- (NSDictionary *)packSHA1sByPackSetNameFromS3:(NSError **)error; +- (NSMutableSet *)diskPackSetNames:(NSError **)error; +- (PackSet *)packSetForName:(NSString *)packSetName error:(NSError **)error; +- (NSArray *)cachedPackSHA1sForPackSet:(NSString *)packSetName error:(NSError **)error; +@end + +@implementation PackSetSet ++ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID { + return [NSString stringWithFormat:@"/%@/%@/packsets", theS3BucketName, theComputerUUID]; +} ++ (NSString *)localPathWithComputerUUID:(NSString *)computerUUID { + return [NSString stringWithFormat:@"%@/%@/packsets", [ArqUserLibrary arqCachesPath], computerUUID]; +} + +- (id)initWithS3Service:(S3Service *)theS3 + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID { + if (self = [super init]) { + s3 = [theS3 retain]; + s3BucketName = [theS3BucketName copy]; + computerUUID = [theComputerUUID copy]; + packSets = [[NSMutableDictionary alloc] init]; + } + return self; +} +- (void)dealloc { + [s3 release]; + [s3BucketName release]; + [computerUUID release]; + [packSets release]; + [super dealloc]; +} +- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName error:(NSError **)error { + PackSet *packSet = [self packSetForName:packSetName error:error]; + if (packSet == nil) { + return nil; + } + return [packSet newServerBlobForSHA1:sha1 error:error]; +} +- (BOOL)containsBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName { + BOOL contains = [[packSets objectForKey:packSetName] containsBlobForSHA1:sha1]; + return contains; +} +- (BOOL)resetFromS3:(NSError **)error { + HSLogDebug(@"resetting pack sets from S3"); + [packSets removeAllObjects]; + NSDictionary *s3PackSHA1sByPackSetName = [self packSHA1sByPackSetNameFromS3:error]; + if (s3PackSHA1sByPackSetName == nil) { + return NO; + } + + // + // Remove disk pack sets that don't exist in S3. + // + NSMutableSet *diskPackSetNames = [self diskPackSetNames:error]; + if (diskPackSetNames == nil) { + return NO; + } + NSMutableSet *s3PackSetNames = [NSMutableSet setWithArray:[s3PackSHA1sByPackSetName allKeys]]; + [diskPackSetNames minusSet:s3PackSetNames]; + for (NSString *bogusDiskPackSetName in diskPackSetNames) { + NSString *packSetPath = [PackSet localPathWithComputerUUID:computerUUID packSetName:bogusDiskPackSetName]; + HSLogDebug(@"removing local pack set that doesn't exist in S3: %@", packSetPath); + if (![[NSFileManager defaultManager] removeItemAtPath:packSetPath error:error]) { + return NO; + } + } + + // + // Create PackSets, make index files local, and load PackIndexEntries into memory. + // + for (NSString *s3PackSetName in [s3PackSHA1sByPackSetName allKeys]) { + NSArray *packSHA1s = [s3PackSHA1sByPackSetName objectForKey:s3PackSetName]; + PackSet *packSet = [[[PackSet alloc] initWithName:s3PackSetName + s3Service:s3 + s3BucketName:s3BucketName + computerUUID:computerUUID + keepPacksLocal:[s3PackSetName hasSuffix:@"-trees"] + packSHA1s:packSHA1s error:error] autorelease]; + if (packSet == nil) { + return NO; + } + [packSets setObject:packSet forKey:s3PackSetName]; + } + return YES; +} +@end +@implementation PackSetSet (internal) +- (NSDictionary *)packSHA1sByPackSetNameFromS3:(NSError **)error { + NSMutableDictionary *packSHA1sByPackSetName = [NSMutableDictionary dictionary]; + NSString *packSetPrefix = [PackSet s3PathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:@""]; + NSArray *s3Paths = [s3 pathsWithPrefix:packSetPrefix error:error]; + if (s3Paths == nil) { + return nil; + } + // Format: ///packsets//.pack + NSString *pattern = [NSString stringWithFormat:@"^%@([^/]+)/(.+)\\.pack$", packSetPrefix]; + for (NSString *s3Path in s3Paths) { + NSRange packSetNameRange = [s3Path rangeOfRegex:pattern capture:1]; + NSRange sha1Range = [s3Path rangeOfRegex:pattern capture:2]; + if (packSetNameRange.location != NSNotFound && sha1Range.location != NSNotFound) { + NSString *escapedPackSetName = [s3Path substringWithRange:packSetNameRange]; + NSString *packSetName = [escapedPackSetName stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *packSHA1 = [s3Path substringWithRange:sha1Range]; + NSMutableArray *packSHA1s = [packSHA1sByPackSetName objectForKey:packSetName]; + if (packSHA1s == nil) { + packSHA1s = [NSMutableArray array]; + [packSHA1sByPackSetName setObject:packSHA1s forKey:packSetName]; + } + [packSHA1s addObject:packSHA1]; + } + } + return packSHA1sByPackSetName; +} +- (NSMutableSet *)diskPackSetNames:(NSError **)error { + NSMutableSet *diskPackSetNames = [NSMutableSet set]; + NSString *packSetsDir = [PackSetSet localPathWithComputerUUID:computerUUID]; + if ([[NSFileManager defaultManager] fileExistsAtPath:packSetsDir]) { + NSArray *packSetNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:packSetsDir error:error]; + if (packSetNames == nil) { + return nil; + } + for (NSString *packSetName in packSetNames) { + if (![packSetName hasPrefix:@"."]) { + [diskPackSetNames addObject:packSetName]; + } + } + } + return diskPackSetNames; +} +- (PackSet *)packSetForName:(NSString *)packSetName error:(NSError **)error { + PackSet *packSet = [packSets objectForKey:packSetName]; + if (packSet == nil) { + NSError *myError; + NSArray *packSHA1s = [self cachedPackSHA1sForPackSet:packSetName error:&myError]; + if (packSHA1s == nil) { + HSLogError(@"error reading cached pack sets: %@", [myError localizedDescription]); + packSHA1s = [NSArray array]; + } + packSet = [[PackSet alloc] initWithName:packSetName + s3Service:s3 + s3BucketName:s3BucketName + computerUUID:computerUUID + keepPacksLocal:[packSetName hasSuffix:@"-trees"] + packSHA1s:packSHA1s + error:error]; + if (packSet == nil) { + return nil; + } + [packSets setObject:packSet forKey:packSetName]; + [packSet release]; + } + return packSet; +} +- (NSArray *)cachedPackSHA1sForPackSet:(NSString *)packSetName error:(NSError **)error { + NSString *packSetDir = [PackSet localPathWithComputerUUID:computerUUID packSetName:packSetName]; + NSMutableArray *ret = [NSMutableArray array]; + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:packSetDir isDirectory:&isDir] && isDir) { + NSArray *dirNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:packSetDir error:error]; + if (dirNames == nil) { + return nil; + } + for (NSString *dirName in dirNames) { + NSString *dir = [packSetDir stringByAppendingPathComponent:dirName]; + if ([[NSFileManager defaultManager] fileExistsAtPath:dir isDirectory:&isDir] && isDir) { + NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:error]; + if (fileNames == nil) { + return nil; + } + for (NSString *fileName in fileNames) { + NSRange sha1Range = [fileName rangeOfRegex:@"^(.+)\\.index$" capture:1]; + if (sha1Range.location != NSNotFound) { + NSString *sha1 = [dirName stringByAppendingString:[fileName substringWithRange:sha1Range]]; + [ret addObject:sha1]; + } + } + } + } + } + return ret; +} +@end diff --git a/RestoreNode.h b/RestoreNode.h new file mode 100644 index 0000000..739d7a7 --- /dev/null +++ b/RestoreNode.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStream.h" +#import "OutputStream.h" +@class Tree; +@class Node; + +@interface RestoreNode : NSObject { + Tree *tree; + NSString *nodeName; + NSString *relativePath; +} +- (id)initWithTree:(Tree *)theTree nodeName:(NSString *)theNodeName relativePath:(NSString *)theRelativePath; +- (Tree *)tree; +- (Node *)node; +- (NSString *)relativePath; +@end diff --git a/RestoreNode.m b/RestoreNode.m new file mode 100644 index 0000000..586eaa2 --- /dev/null +++ b/RestoreNode.m @@ -0,0 +1,63 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "RestoreNode.h" +#import "Tree.h" +#import "Node.h" +#import "StringIO.h" + +@implementation RestoreNode +- (id)initWithTree:(Tree *)theTree nodeName:(NSString *)theNodeName relativePath:(NSString *)theRelativePath { + if (self = [super init]) { + tree = [theTree retain]; + nodeName = [theNodeName copy]; + NSAssert((nodeName == nil) || ([tree childNodeWithName:nodeName] != nil), @"node doesn't exist in Tree!"); + relativePath = [theRelativePath copy]; + } + return self; +} +- (void)dealloc { + [tree release]; + [nodeName release]; + [relativePath release]; + [super dealloc]; +} +- (Tree *)tree { + return tree; +} +- (Node *)node { + return [tree childNodeWithName:nodeName]; +} +- (NSString *)relativePath { + return relativePath; +} +@end diff --git a/Restorer.h b/Restorer.h new file mode 100644 index 0000000..2452bb9 --- /dev/null +++ b/Restorer.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class S3Service; +@class S3Fark; +@class S3Repo; + +@interface Restorer : NSObject { + S3Fark *fark; + S3Repo *repo; + NSString *bucketName; + NSString *rootPath; + NSMutableArray *restoreNodes; + NSMutableDictionary *hardlinks; +} +- (id)initWithS3Service:(S3Service *)theS3 s3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID bucketName:(NSString *)theBucketName encryptionKey:(NSString *)theEncryptionKey; +- (BOOL)restore:(NSError **)error; +@end diff --git a/Restorer.m b/Restorer.m new file mode 100644 index 0000000..367e6c8 --- /dev/null +++ b/Restorer.m @@ -0,0 +1,467 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#include +#import "Restorer.h" +#import "S3Fark.h" +#import "S3Repo.h" +#import "SetNSError.h" +#import "Tree.h" +#import "Node.h" +#import "RestoreNode.h" +#import "FileAttributes.h" +#import "NSData-InputStream.h" +#import "DataInputStream.h" +#import "XAttrSet.h" +#import "FileOutputStream.h" +#import "NSFileManager_extra.h" +#import "CFStreamPair.h" +#import "NSErrorCodes.h" + +@interface Restorer (internal) +- (BOOL)addRestoreNodesForTreeSHA1:(NSString *)treeSHA1 relativePath:(NSString *)relativePath error:(NSError **)error; +- (BOOL)restoreRestoreNode:(RestoreNode *)rn error:(NSError **)error; +- (BOOL)createFile:(Node *)node atPath:(NSString *)path error:(NSError **)error; +- (BOOL)createFileAtPath:(NSString *)path fromSHA1s:(NSArray *)dataSHA1s error:(NSError **)error; +- (BOOL)createFileOnceAtPath:(NSString *)path fromSHA1s:(NSArray *)dataSHA1s error:(NSError **)error; +- (BOOL)appendBlobForSHA1:(NSString *)sha1 toFile:(FileOutputStream *)fos error:(NSError **)error; +- (BOOL)applyTree:(Tree *)tree toPath:(NSString *)restorePath error:(NSError **)error; +- (BOOL)applyNode:(Node *)node toPath:(NSString *)restorePath error:(NSError **)error; +- (BOOL)applyACLSHA1:(NSString *)aclSHA1 toFileAttributes:(FileAttributes *)fa error:(NSError **)error; +- (BOOL)applyXAttrsSHA1:(NSString *)xattrsSHA1 toFile:(NSString *)path error:(NSError **)error; +- (BOOL)createSymLink:(Node *)node path:(NSString *)symLinkFile target:(NSString *)target error:(NSError **)error; +@end + +@implementation Restorer +- (id)initWithS3Service:(S3Service *)theS3 s3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID bucketName:(NSString *)theBucketName encryptionKey:(NSString *)theEncryptionKey { + if (self = [super init]) { + fark = [[S3Fark alloc] initWithS3Service:theS3 s3BucketName:theS3BucketName computerUUID:theComputerUUID]; + repo = [[S3Repo alloc] initWithS3Service:theS3 s3BucketName:theS3BucketName computerUUID:theComputerUUID bucketUUID:theBucketUUID encrypted:YES encryptionKey:theEncryptionKey fark:fark ensureCacheIntegrity:NO]; + bucketName = [theBucketName copy]; + rootPath = [[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:theBucketName] copy]; + restoreNodes = [[NSMutableArray alloc] init]; + hardlinks = [[NSMutableDictionary alloc] init]; + } + return self; +} +- (void)dealloc { + [fark release]; + [repo release]; + [bucketName release]; + [rootPath release]; + [restoreNodes release]; + [hardlinks release]; + [super dealloc]; +} +- (BOOL)restore:(NSError **)error { + if ([[NSFileManager defaultManager] fileExistsAtPath:rootPath]) { + SETNSERROR(@"RestorerErrorDomain", -1, @"%@ already exists", rootPath); + return NO; + } + if (![fark reloadPacksFromS3:error]) { + return NO; + } + if (![[NSFileManager defaultManager] createDirectoryAtPath:rootPath withIntermediateDirectories:YES attributes:nil error:error]) { + HSLogError(@"failed to create directory %@", rootPath); + return NO; + } + NSString *headSHA1 = nil; + if (![repo localHeadSHA1:&headSHA1 error:error]) { + return NO; + } + if (headSHA1 == nil) { + SETNSERROR(@"RestorerErrorDomain", -1, @"no backup found"); + return NO; + } + Commit *head = nil; + if (![repo commit:&head forSHA1:headSHA1 error:error]) { + return NO; + } + if (![self addRestoreNodesForTreeSHA1:[head treeSHA1] relativePath:@"" error:error]) { + return NO; + } + for (RestoreNode *rn in restoreNodes) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSError *myError = nil; + BOOL ret = [self restoreRestoreNode:rn error:&myError]; + [myError retain]; + [pool drain]; + [myError autorelease]; + if (!ret) { + if (error != NULL) { + *error = myError; + } + return NO; + } + } + return YES; +} +@end + +@implementation Restorer (internal) +- (BOOL)addRestoreNodesForTreeSHA1:(NSString *)treeSHA1 relativePath:(NSString *)relativePath error:(NSError **)error { + Tree *tree = nil; + if (![repo tree:&tree forSHA1:treeSHA1 error:error]) { + return NO; + } + RestoreNode *treeRN = [[RestoreNode alloc] initWithTree:tree nodeName:nil relativePath:relativePath]; + [restoreNodes addObject:treeRN]; + [treeRN release]; + for (NSString *childNodeName in [tree childNodeNames]) { + Node *childNode = [tree childNodeWithName:childNodeName]; + NSString *childRelativePath = [NSString stringWithFormat:@"%@/%@", relativePath, childNodeName]; + if ([childNode isTree]) { + if (![self addRestoreNodesForTreeSHA1:[childNode treeSHA1] relativePath:childRelativePath error:error]) { + return NO; + } + } else { + RestoreNode *childRN = [[RestoreNode alloc] initWithTree:tree nodeName:childNodeName relativePath:childRelativePath]; + [restoreNodes addObject:childRN]; + [childRN release]; + } + } + return YES; +} +- (BOOL)restoreRestoreNode:(RestoreNode *)rn error:(NSError **)error { + printf("restoring %s%s\n", [bucketName UTF8String], [[rn relativePath] UTF8String]); + NSString *restorePath = [rootPath stringByAppendingPathComponent:[rn relativePath]]; + NSString *parentPath = [restorePath stringByDeletingLastPathComponent]; + if (![[NSFileManager defaultManager] fileExistsAtPath:parentPath] + && ![[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:error]) { + HSLogError(@"failed to create directory %@", parentPath); + return NO; + } + BOOL createdFile = NO; + int nlink = [rn node] == nil ? [[rn tree] st_nlink] : [[rn node] st_nlink]; + if (nlink > 1) { + int ino = [rn node] == nil ? [[rn tree] st_ino] : [[rn node] st_ino]; + NSNumber *inode = [NSNumber numberWithInt:ino]; + RestoreNode *existing = [hardlinks objectForKey:inode]; + if (existing != nil) { + // Link. + NSString *existingPath = [rootPath stringByAppendingPathComponent:[existing relativePath]]; + if (([existing node] == nil) != ([rn node] == nil)) { + SETNSERROR(@"RestoreErrorDomain", -1, @"cannot link a directory to a file"); + HSLogError(@"can't link directory to a file"); + return NO; + } + if (link([existingPath fileSystemRepresentation], [restorePath fileSystemRepresentation]) == -1) { + SETNSERROR(@"RestoreErrorDomain", -1, @"link(%@,%@): %s", existingPath, restorePath, strerror(errno)); + HSLogError(@"link() failed"); + return NO; + } + createdFile = YES; + } else { + [hardlinks setObject:rn forKey:inode]; + } + } + if (!createdFile) { + Node *node = [rn node]; + if (node == nil) { + Tree *tree = [rn tree]; + if (![[NSFileManager defaultManager] fileExistsAtPath:restorePath] && ![[NSFileManager defaultManager] createDirectoryAtPath:restorePath withIntermediateDirectories:NO attributes:nil error:error]) { + HSLogError(@"error creating %@", restorePath); + return NO; + } + if (![self applyTree:tree toPath:restorePath error:error]) { + HSLogError(@"applyTree error"); + return NO; + } + } else { + int mode = [node mode]; + BOOL isFifo = (mode & S_IFIFO) == S_IFIFO; + if (isFifo) { + if (mkfifo([restorePath fileSystemRepresentation], mode) == -1) { + SETNSERROR(@"RestoreErrorDomain", errno, @"mkfifo(%@): %s", restorePath, strerror(errno)); + return NO; + } + if (![self applyNode:node toPath:restorePath error:error]) { + HSLogError(@"applyNode error"); + return NO; + } + } else if ((mode & S_IFSOCK) == S_IFSOCK) { + // Skip socket -- restoring it doesn't make any sense. + } else if ((mode & S_IFREG) == 0 && ((mode & S_IFCHR) == S_IFCHR || (mode & S_IFBLK) == S_IFBLK)) { + if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:restorePath error:error]) { + return NO; + } + if (mknod([restorePath fileSystemRepresentation], mode, [node st_rdev]) == -1) { + SETNSERROR(@"RestorerErrorDomain", -1, @"mknod(%@): %s", restorePath, strerror(errno)); + return NO; + } + } else { + if (![self createFile:node atPath:restorePath error:error]) { + HSLogError(@"createFile error"); + return NO; + } + if (![self applyNode:node toPath:restorePath error:error]) { + HSLogError(@"applyNode error"); + return NO; + } + } + } + FileAttributes *fa = [[[FileAttributes alloc] initWithPath:restorePath error:error] autorelease]; + if (fa == nil) { + return NO; + } + int flags = [fa flags]; + if (flags) { + // Clear the flags temporarily so we can change ownership of the file. + if (![fa applyFlags:0 error:error]) { + return NO; + } + } + int uid = [rn node] == nil ? [[rn tree] uid] : [[rn node] uid]; + int gid = [rn node] == nil ? [[rn tree] gid] : [[rn node] gid]; + NSError *chownError; + if (![fa applyUID:uid gid:gid error:&chownError]) { + fprintf(stderr, "error applying UID and GID to %s: %s\n", [restorePath fileSystemRepresentation], [[chownError localizedDescription] UTF8String]); + } + if (flags) { + if (![fa applyFlags:flags error:error]) { + return NO; + } + } + } + return YES; +} +- (BOOL)applyTree:(Tree *)tree toPath:(NSString *)path error:(NSError **)error { + FileAttributes *fa = [[[FileAttributes alloc] initWithPath:path error:error] autorelease]; + if (!fa) { + return NO; + } + if (![fa applyFinderFlags:[tree finderFlags] error:error] + || ![fa applyExtendedFinderFlags:[tree extendedFinderFlags] error:error]) { + return NO; + } + if (![self applyACLSHA1:[tree aclSHA1] toFileAttributes:fa error:error]) { + return NO; + } + if (![self applyXAttrsSHA1:[tree xattrsSHA1] toFile:path error:error]) { + return NO; + } + if (([tree mode] & (S_ISUID|S_ISGID|S_ISVTX) != 0) && ![fa applyMode:[tree mode] error:error]) { + return NO; + } + if (([tree mode] & S_IFLNK) != S_IFLNK && [tree treeVersion] >= 7 && ![fa applyMTimeSec:tree.mtime_sec mTimeNSec:tree.mtime_nsec error:error]) { + return NO; + } + if (([tree treeVersion] >= 7) && ![fa applyCreateTimeSec:tree.createTime_sec createTimeNSec:tree.createTime_nsec error:error]) { + return NO; + } + if (![fa applyFlags:[tree flags] error:error]) { + return NO; + } + return YES; +} +- (BOOL)applyNode:(Node *)node toPath:(NSString *)path error:(NSError **)error { + FileAttributes *fa = [[[FileAttributes alloc] initWithPath:path error:error] autorelease]; + if (!fa) { + return NO; + } + if (![self applyACLSHA1:[node aclSHA1] toFileAttributes:fa error:error]) { + return NO; + } + BOOL isFifo = ([node mode] & S_IFIFO) == S_IFIFO; + if (!isFifo) { + if (![fa applyFinderFlags:[node finderFlags] error:error] + || ![fa applyExtendedFinderFlags:[node extendedFinderFlags] error:error] + || ![self applyXAttrsSHA1:[node xattrsSHA1] toFile:path error:error] + || ![fa applyFinderFileType:[node finderFileType] finderFileCreator:[node finderFileCreator] error:error]) { + return NO; + } + } + if (([node mode] & (S_ISUID|S_ISGID|S_ISVTX) != 0) && ![fa applyMode:[node mode] error:error]) { + return NO; + } + if (([node mode] & S_IFLNK) != S_IFLNK && [node treeVersion] >= 7 && ![fa applyMTimeSec:node.mtime_sec mTimeNSec:node.mtime_nsec error:error]) { + return NO; + } + if (([node treeVersion] >= 7) && ![fa applyCreateTimeSec:node.createTime_sec createTimeNSec:node.createTime_nsec error:error]) { + return NO; + } + if (!isFifo) { + if (![fa applyFlags:[node flags] error:error]) { + return NO; + } + } + return YES; +} +- (BOOL)createFile:(Node *)node atPath:(NSString *)path error:(NSError **)error { + if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:path error:error]) { + HSLogError(@"error ensuring path %@ exists", path); + return NO; + } + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + if (![[NSFileManager defaultManager] removeItemAtPath:path error:error]) { + HSLogError(@"error removing existing file %@", path); + return NO; + } + } + HSLogTrace(@"%qu bytes -> %@", [node dataSize], path); + if (([node mode] & S_IFLNK) == S_IFLNK) { + NSData *data = [repo dataForSHA1s:[node dataSHA1s] error:error]; + if (data == nil) { + HSLogError(@"error getting data for %@", [node dataSHA1s]); + return NO; + } + NSString *target = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + if (![self createSymLink:node path:path target:target error:error]) { + HSLogError(@"error creating sym link %@", path); + return NO; + } + } else if ([node dataSize] > 0) { + if (![self createFileAtPath:path fromSHA1s:[node dataSHA1s] error:error]) { + return NO; + } + } else { + // It's a zero-byte file. + int fd = open([path fileSystemRepresentation], O_CREAT|O_EXCL, [node mode]); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s: %@", strerror(errno), path); + HSLogError(@"error opening %@", path); + return NO; + } + close(fd); + } + return YES; +} +- (BOOL)createFileAtPath:(NSString *)path fromSHA1s:(NSArray *)dataSHA1s error:(NSError **)error { + BOOL ret = YES; + for (;;) { + NSError *myError = nil; + if (![self createFileOnceAtPath:path fromSHA1s:dataSHA1s error:&myError]) { + if ([[myError domain] isEqualToString:[CFStreamPair errorDomain]]) { + HSLogDebug(@"network error restoring %@ (retrying): %@", path, [myError localizedDescription]); + } else { + if (error != NULL) { + *error = myError; + } + ret = NO; + break; + } + } else { + break; + } + } + return ret; +} +- (BOOL)createFileOnceAtPath:(NSString *)path fromSHA1s:(NSArray *)dataSHA1s error:(NSError **)error { + FileOutputStream *fos = [[FileOutputStream alloc] initWithPath:path append:NO]; + BOOL ret = YES; + for (NSString *sha1 in dataSHA1s) { + if (![self appendBlobForSHA1:sha1 toFile:fos error:error]) { + ret = NO; + break; + } + } + [fos release]; + return ret; +} +- (BOOL)appendBlobForSHA1:(NSString *)sha1 toFile:(FileOutputStream *)fos error:(NSError **)error { + ServerBlob *dataBlob = [repo newServerBlobForSHA1:sha1 error:error]; + if (dataBlob == nil) { + HSLogError(@"error getting server blob for %@", sha1); + return NO; + } + id is = [dataBlob newInputStream]; + [dataBlob release]; + BOOL ret = YES; + for (;;) { + NSUInteger received = 0; + NSError *myError = nil; + unsigned char *buf = [is read:&received error:&myError]; + if (buf == nil) { + if ([myError code] != ERROR_EOF) { + ret = NO; + HSLogError(@"error reading from stream for blob %@: %@", sha1, [myError localizedDescription]); + if (error != NULL) { + *error = myError; + } + } + break; + } + if (![fos write:buf length:received error:error]) { + ret = NO; + break; + } + [NSThread sleepForTimeInterval:0.01]; + } + [is release]; + return ret; +} +- (BOOL)createSymLink:(Node *)node path:(NSString *)symLinkFile target:(NSString *)target error:(NSError **)error { + struct stat st; + if (lstat([symLinkFile fileSystemRepresentation], &st) == 0) { + if (![[NSFileManager defaultManager] removeItemAtPath:symLinkFile error:error]) { + return NO; + } + } + if (symlink([target fileSystemRepresentation], [symLinkFile fileSystemRepresentation]) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"symlink(%@): %s", symLinkFile, strerror(errno)); + return NO; + } + return YES; +} +- (BOOL)applyACLSHA1:(NSString *)aclSHA1 toFileAttributes:(FileAttributes *)fa error:(NSError **)error { + if (aclSHA1 != nil) { + NSData *data = [repo dataForSHA1:aclSHA1 error:error]; + if (data == nil) { + return NO; + } + NSString *aclString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + if (![fa applyAcl:aclString error:error]) { + return NO; + } + } + return YES; +} +- (BOOL)applyXAttrsSHA1:(NSString *)xattrsSHA1 toFile:(NSString *)path error:(NSError **)error { + if (xattrsSHA1 != nil) { + NSData *xattrsData = [repo dataForSHA1:xattrsSHA1 error:error]; + if (xattrsData == nil) { + return NO; + } + DataInputStream *is = [xattrsData newInputStream]; + XAttrSet *set = [[[XAttrSet alloc] initWithBufferedInputStream:is error:error] autorelease]; + [is release]; + if (!set) { + return NO; + } + if (![set applyToFile:path error:error]) { + return NO; + } + } + return YES; +} +@end diff --git a/S3Fark.h b/S3Fark.h new file mode 100644 index 0000000..e8f6c54 --- /dev/null +++ b/S3Fark.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class S3Service; +@class Blob; +@class ServerBlob; +@class PackSetSet; + +@interface S3Fark : NSObject { + S3Service *s3; + NSString *s3BucketName; + NSString *computerUUID; + NSThread *creatorThread; + PackSetSet *packSetSet; +} +- (id)initWithS3Service:(S3Service *)theS3 + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID; +- (NSData *)dataForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName searchPackOnly:(BOOL)searchPackOnly error:(NSError **)error; +- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName searchPackOnly:(BOOL)searchPackOnly error:(NSError **)error; +- (BOOL)containsBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName searchPackOnly:(BOOL)searchPackOnly; +- (BOOL)reloadPacksFromS3:(NSError **)error; +@end diff --git a/S3Fark.m b/S3Fark.m new file mode 100644 index 0000000..6596850 --- /dev/null +++ b/S3Fark.m @@ -0,0 +1,109 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "S3Service.h" +#import "S3Fark.h" +#import "SetNSError.h" +#import "NSErrorCodes.h" +#import "PackSetSet.h" +#import "ServerBlob.h" + +@interface S3Fark (internal) +- (NSString *)pathForSHA1:(NSString *)sha1; +@end + +@implementation S3Fark +- (id)initWithS3Service:(S3Service *)theS3 + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID { + if (self = [super init]) { + s3 = [theS3 retain]; + s3BucketName = [theS3BucketName copy]; + computerUUID = [theComputerUUID copy]; + creatorThread = [[NSThread currentThread] retain]; + packSetSet = [[PackSetSet alloc] initWithS3Service:s3 s3BucketName:s3BucketName computerUUID:computerUUID]; + } + return self; +} +- (void)dealloc { + [s3 release]; + [s3BucketName release]; + [computerUUID release]; + [creatorThread release]; + [packSetSet release]; + [super dealloc]; +} +- (NSData *)dataForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName searchPackOnly:(BOOL)searchPackOnly error:(NSError **)error { + ServerBlob *sb = [self newServerBlobForSHA1:sha1 packSetName:packSetName searchPackOnly:searchPackOnly error:error]; + if (sb == nil) { + return nil; + } + NSData *data = [sb slurp:error]; + [sb release]; + return data; +} +- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName searchPackOnly:(BOOL)searchPackOnly error:(NSError **)error { + NSAssert([NSThread currentThread] == creatorThread, @"must be on same thread!"); + NSError *myError = nil; + ServerBlob *sb = [packSetSet newServerBlobForSHA1:sha1 packSetName:packSetName error:&myError]; + if (sb == nil) { + if ([myError code] != ERROR_NOT_FOUND) { + HSLogError(@"error reading sha1 %@ from packSetSet: %@", sha1, [myError localizedDescription]); + } + if (error != NULL) { + *error = myError; + } + if (!searchPackOnly) { + sb = [s3 newServerBlobAtPath:[self pathForSHA1:sha1] error:error]; + } + } + return sb; +} +- (BOOL)containsBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName searchPackOnly:(BOOL)searchPackOnly { + NSAssert([NSThread currentThread] == creatorThread, @"must be on same thread!"); + BOOL contains = [packSetSet containsBlobForSHA1:sha1 packSetName:packSetName]; + if (!contains && !searchPackOnly) { + contains = [s3 containsBlobAtPath:[self pathForSHA1:sha1]]; + } + return contains; +} +- (BOOL)reloadPacksFromS3:(NSError **)error { + NSAssert([NSThread currentThread] == creatorThread, @"must be on same thread!"); + return [packSetSet resetFromS3:error]; +} +@end + +@implementation S3Fark (internal) +- (NSString *)pathForSHA1:(NSString *)sha1 { + return [NSString stringWithFormat:@"/%@/%@/objects/%@", s3BucketName, computerUUID, sha1]; +} +@end diff --git a/S3Repo.h b/S3Repo.h new file mode 100644 index 0000000..f9d4264 --- /dev/null +++ b/S3Repo.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class S3Service; +@class S3Fark; +@class Commit; +@class Tree; +@class Blob; +@class ServerBlob; + + +@interface S3Repo : NSObject { + S3Service *s3; + NSString *s3BucketName; + NSString *computerUUID; + NSString *bucketUUID; + S3Fark *fark; + BOOL encrypted; + NSString *encryptionKey; + BOOL ensureCacheIntegrity; + NSString *treesPackSetName; + NSString *blobsPackSetName; +} ++ (NSString *)errorDomain; + +- (id)initWithS3Service:(S3Service *)theS3 + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID + bucketUUID:(NSString *)theBucketUUID + encrypted:(BOOL)isEncrypted + encryptionKey:(NSString *)theEncryptionKey + fark:(S3Fark *)theFark + ensureCacheIntegrity:(BOOL)ensure; + +- (BOOL)localHeadSHA1:(NSString **)localHeadSHA1 error:(NSError **)error; + +// Returns NO if commit not found: +- (BOOL)commit:(Commit **)commit forSHA1:(NSString *)theSHA1 error:(NSError **)error; + +// Returns NO if commit not found: +- (BOOL)tree:(Tree **)tree forSHA1:(NSString *)theSHA1 error:(NSError **)error; + +- (BOOL)containsBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName searchPackOnly:(BOOL)searchPackOnly; +- (NSData *)dataForSHA1:(NSString *)sha1 error:(NSError **)error; +- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error; +- (NSData *)dataForSHA1s:(NSArray *)sha1s error:(NSError **)error; +- (BOOL)commonAncestorCommitSHA1:(NSString **)ancestorSHA1 forCommitSHA1:(NSString *)commit0SHA1 andCommitSHA1:(NSString *)commit1SHA1 error:(NSError **)error; +- (BOOL)is:(BOOL *)isAncestor commitSHA1:(NSString *)descendantSHA1 ancestorOfCommitSHA1:(NSString *)sha1 error:(NSError **)error; +- (NSString *)localHeadS3Path; +- (BOOL)isEncrypted; +- (NSString *)blobsPackSetName; +- (NSSet *)packSetNames; +@end diff --git a/S3Repo.m b/S3Repo.m new file mode 100644 index 0000000..eff288c --- /dev/null +++ b/S3Repo.m @@ -0,0 +1,291 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "ServerBlob.h" +#import "S3Repo.h" +#import "S3Service.h" +#import "S3Fark.h" +#import "SetNSError.h" +#import "Tree.h" +#import "Commit.h" +#import "NSData-Encrypt.h" +#import "DecryptedInputStream.h" +#import "InputStreams.h" +#import "NSErrorCodes.h" +#import "NSData-InputStream.h" +#import "DataInputStream.h" +#import "HTTP.h" + +@interface S3Repo (internal) +- (NSData *)dataForSHA1:(NSString *)sha1 packSetName:(NSString *)thePackSetName error:(NSError **)error; +- (NSData *)dataForSHA1:(NSString *)sha1 packSetName:(NSString *)thePackSetName searchPackOnly:(BOOL)packOnly error:(NSError **)error; +- (BOOL)ancestorCommitSHA1sForCommitSHA1:(NSString *)commitSHA1 outArray:(NSMutableArray *)arr error:(NSError **)error; +- (BOOL)commitSHA1:(NSString **)sha1 fromSHA1:(NSString *)fromSHA1 before:(NSDate *)date error:(NSError **)error; +@end + +static NSString *ERROR_DOMAIN = @"S3RepoErrorDomain"; + +@implementation S3Repo ++ (NSString *)errorDomain { + return ERROR_DOMAIN; +} +- (id)initWithS3Service:(S3Service *)theS3 + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID + bucketUUID:(NSString *)theBucketUUID + encrypted:(BOOL)isEncrypted + encryptionKey:(NSString *)theEncryptionKey + fark:(S3Fark *)theFark + ensureCacheIntegrity:(BOOL)ensure { + if (self = [super init]) { + s3 = [theS3 retain]; + s3BucketName = [theS3BucketName copy]; + computerUUID = [theComputerUUID copy]; + bucketUUID = [theBucketUUID copy]; + encrypted = isEncrypted; + encryptionKey = [theEncryptionKey copy]; + fark = [theFark retain]; + ensureCacheIntegrity = ensure; + treesPackSetName = [[NSString alloc] initWithFormat:@"%@-trees", bucketUUID]; + blobsPackSetName = [[NSString alloc] initWithFormat:@"%@-blobs", bucketUUID]; + } + return self; +} +- (void)dealloc { + [s3 release]; + [s3BucketName release]; + [computerUUID release]; + [bucketUUID release]; + [encryptionKey release]; + [fark release]; + [treesPackSetName release]; + [blobsPackSetName release]; + [super dealloc]; +} +- (BOOL)localHeadSHA1:(NSString **)headSHA1 error:(NSError **)error { + *headSHA1 = nil; + NSError *myError; + NSData *headSHA1Data = [s3 dataAtPath:[self localHeadS3Path] error:&myError]; + if (headSHA1Data == nil && !([[myError domain] isEqualToString:[S3Service serverErrorDomain]] && [myError code] == HTTP_NOT_FOUND)) { + if (error != NULL) { + *error = myError; + } + return NO; + } + if (headSHA1Data != nil) { + *headSHA1 = [[[NSString alloc] initWithData:headSHA1Data encoding:NSUTF8StringEncoding] autorelease]; + } + return YES; +} +- (BOOL)commit:(Commit **)commit forSHA1:(NSString *)theSHA1 error:(NSError **)error { + *commit = nil; + NSData *data = [self dataForSHA1:theSHA1 packSetName:treesPackSetName searchPackOnly:YES error:error]; + if (data == nil) { + HSLogDebug(@"commit data not found for %@", theSHA1); + return NO; + } + id is = [data newInputStream]; + *commit = [[[Commit alloc] initWithBufferedInputStream:is error:error] autorelease]; + [is release]; + if (*commit == nil) { + return NO; + } + return YES; +} +- (BOOL)tree:(Tree **)tree forSHA1:(NSString *)theSHA1 error:(NSError **)error { + *tree = nil; + NSData *data = [self dataForSHA1:theSHA1 packSetName:treesPackSetName searchPackOnly:YES error:error]; + if (data == nil) { + HSLogDebug(@"tree data not found for %@", theSHA1); + return NO; + } + id is = [data newInputStream]; + *tree = [[[Tree alloc] initWithBufferedInputStream:is error:error] autorelease]; + [is release]; + if (*tree == nil) { + return NO; + } + return YES; +} +- (BOOL)containsBlobForSHA1:(NSString *)sha1 packSetName:(NSString *)packSetName searchPackOnly:(BOOL)searchPackOnly { + return [fark containsBlobForSHA1:sha1 packSetName:packSetName searchPackOnly:searchPackOnly]; +} +- (NSData *)dataForSHA1:(NSString *)sha1 error:(NSError **)error { + NSData *data = [fark dataForSHA1:sha1 packSetName:treesPackSetName searchPackOnly:YES error:error]; + if (data == nil) { + data = [fark dataForSHA1:sha1 packSetName:blobsPackSetName searchPackOnly:NO error:error]; + } + if (data != nil && encrypted) { + data = [data decryptWithCipher:ARQ_DEFAULT_CIPHER_NAME key:encryptionKey error:error]; + } + return data; +} +- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error { + ServerBlob *sb = [fark newServerBlobForSHA1:sha1 packSetName:treesPackSetName searchPackOnly:NO error:error]; + if (sb == nil) { + sb = [fark newServerBlobForSHA1:sha1 packSetName:blobsPackSetName searchPackOnly:NO error:error]; + } + if (sb != nil && encrypted) { + id is = [sb newInputStream]; + NSString *mimeType = [sb mimeType]; + NSString *downloadName = [sb downloadName]; + [sb release]; + DecryptedInputStream *dis = [[DecryptedInputStream alloc] initWithInputStream:is cipherName:ARQ_DEFAULT_CIPHER_NAME key:encryptionKey error:error]; + [is release]; + if (dis == nil) { + return NO; + } + sb = [[ServerBlob alloc] initWithInputStream:dis mimeType:mimeType downloadName:downloadName]; + [dis release]; + } + return sb; +} +- (NSData *)dataForSHA1s:(NSArray *)sha1s error:(NSError **)error { + NSMutableData *data = [NSMutableData data]; + for (NSString *sha1 in sha1s) { + ServerBlob *sb = [self newServerBlobForSHA1:sha1 error:error]; + if (sb == nil) { + return NO; + } + NSData *blobData = [sb slurp:error]; + [sb release]; + if (blobData == nil) { + return NO; + } + //FIXME: Get rid of this extra copying of data. + [data appendData:blobData]; + } + return data; +} +- (BOOL)commonAncestorCommitSHA1:(NSString **)ancestorSHA1 forCommitSHA1:(NSString *)commit0SHA1 andCommitSHA1:(NSString *)commit1SHA1 error:(NSError **)error { + //FIXME: This is slow and memory-intensive! + NSMutableArray *commit0ParentSHA1s = [[[NSMutableArray alloc] initWithObjects:commit0SHA1, nil] autorelease]; + NSMutableArray *commit1ParentSHA1s = [[[NSMutableArray alloc] initWithObjects:commit1SHA1, nil] autorelease]; + if (![self ancestorCommitSHA1sForCommitSHA1:commit0SHA1 outArray:commit0ParentSHA1s error:error]) { + return NO; + } + if (![self ancestorCommitSHA1sForCommitSHA1:commit1SHA1 outArray:commit1ParentSHA1s error:error]) { + return NO; + } + for (NSString *parent in commit1ParentSHA1s) { + if ([commit0ParentSHA1s containsObject:parent]) { + *ancestorSHA1 = parent; + break; + } + } + return YES; +} +- (BOOL)is:(BOOL *)isAncestor commitSHA1:(NSString *)ancestorCommitSHA1 ancestorOfCommitSHA1:(NSString *)sha1 error:(NSError **)error { + *isAncestor = NO; + if ([ancestorCommitSHA1 isEqualToString:sha1]) { + return YES; + } + //TODO: Get rid of recursion in this method: + Commit *commit = nil; + if (![self commit:&commit forSHA1:sha1 error:error]) { + return NO; + } + for (NSString *parentCommitSHA1 in [commit parentCommitSHA1s]) { + if (![self is:isAncestor commitSHA1:parentCommitSHA1 ancestorOfCommitSHA1:ancestorCommitSHA1 error:error]) { + return NO; + } + if (*isAncestor) { + return YES; + } + } + return YES; +} +- (NSString *)localHeadS3Path { + return [NSString stringWithFormat:@"/%@/%@/bucketdata/%@/refs/heads/master", s3BucketName, computerUUID, bucketUUID]; +} +- (BOOL)isEncrypted { + return encrypted; +} +- (NSString *)blobsPackSetName { + return blobsPackSetName; +} +- (NSSet *)packSetNames { + return [NSSet setWithObjects:blobsPackSetName, treesPackSetName, nil]; +} + +#pragma mark NSObject protocol +- (NSString *)description { + return [NSString stringWithFormat:@"", self, bucketUUID]; +} +@end + +@implementation S3Repo (internal) +- (NSData *)dataForSHA1:(NSString *)sha1 packSetName:(NSString *)thePackSetName error:(NSError **)error { + return [self dataForSHA1:sha1 packSetName:thePackSetName searchPackOnly:NO error:error]; +} +- (NSData *)dataForSHA1:(NSString *)sha1 packSetName:(NSString *)thePackSetName searchPackOnly:(BOOL)packOnly error:(NSError **)error { + NSData *data = [fark dataForSHA1:sha1 packSetName:thePackSetName searchPackOnly:packOnly error:error]; + if (data != nil && encrypted) { + data = [data decryptWithCipher:ARQ_DEFAULT_CIPHER_NAME key:encryptionKey error:error]; + } + return data; +} +- (BOOL)ancestorCommitSHA1sForCommitSHA1:(NSString *)commitSHA1 outArray:(NSMutableArray *)arr error:(NSError **)error { + Commit *commit = nil; + if (![self commit:&commit forSHA1:commitSHA1 error:error]) { + return NO; + } + for (NSString *parentCommitSHA1 in [commit parentCommitSHA1s]) { + [arr addObject:parentCommitSHA1]; + if (![self ancestorCommitSHA1sForCommitSHA1:parentCommitSHA1 outArray:arr error:error]) { + return NO; + } + } + return YES; +} +- (BOOL)commitSHA1:(NSString **)sha1 fromSHA1:(NSString *)fromSHA1 before:(NSDate *)date error:(NSError **)error { + HSLogDebug(@"looking for Commit before %@", [date description]); + *sha1 = nil; + for (;;) { + Commit *commit = nil; + if (![self commit:&commit forSHA1:fromSHA1 error:error]) { + return NO; + } + NSDate *creationDate = [commit creationDate]; + if ([date earlierDate:creationDate] == creationDate) { + *sha1 = [[fromSHA1 retain] autorelease]; + HSLogDebug(@"returning Commit SHA1 %@: creationDate=%@", *sha1, creationDate); + break; + } + if ([[commit parentCommitSHA1s] count] == 0) { + break; + } + fromSHA1 = [[[commit parentCommitSHA1s] allObjects] objectAtIndex:0]; + } + return YES; +} +@end diff --git a/Tree.h b/Tree.h new file mode 100644 index 0000000..9dce379 --- /dev/null +++ b/Tree.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "Blob.h" +#import "BufferedInputStream.h" +#import "OutputStream.h" +@class Node; +@class MutableS3Repo; + +#define CURRENT_TREE_VERSION 10 + +@interface Tree : NSObject { + int treeVersion; + NSString *xattrsSHA1; + unsigned long long xattrsSize; + NSString *aclSHA1; + int uid; + int gid; + int mode; + long long mtime_sec; + long long mtime_nsec; + long long flags; + int finderFlags; + int extendedFinderFlags; + int st_dev; + int st_ino; + uint32_t st_nlink; // in struct stat, it's only 16 bits. + int st_rdev; + int64_t ctime_sec; + int64_t ctime_nsec; + int64_t createTime_sec; + int64_t createTime_nsec; + int64_t st_blocks; + uint32_t st_blksize; + NSMutableDictionary *nodes; +} +- (id)init; +- (id)initWithBufferedInputStream:(id )is error:(NSError **)error; +- (NSArray *)childNodeNames; +- (Node *)childNodeWithName:(NSString *)name; +- (BOOL)containsNodeNamed:(NSString *)name; + +@property(readonly,copy) NSString *xattrsSHA1; +@property(readonly) unsigned long long xattrsSize; +@property(readonly,copy) NSString *aclSHA1; +@property(readonly) int uid; +@property(readonly) int gid; +@property(readonly) int mode; +@property(readonly) long long mtime_sec; +@property(readonly) long long mtime_nsec; +@property(readonly) long long flags; +@property(readonly) int finderFlags; +@property(readonly) int extendedFinderFlags; +@property(readonly) int treeVersion; +@property(readonly) int st_rdev; +@property(readonly) long long ctime_sec; +@property(readonly) long long ctime_nsec; +@property(readonly) long long createTime_sec; +@property(readonly) long long createTime_nsec; +@property(readonly) uint32_t st_nlink; +@property(readonly) int st_ino; +@end diff --git a/Tree.m b/Tree.m new file mode 100644 index 0000000..d6650fe --- /dev/null +++ b/Tree.m @@ -0,0 +1,156 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "StringIO.h" +#import "IntegerIO.h" +#import "BooleanIO.h" +#import "Node.h" +#import "Tree.h" +#import "Blob.h" +#import "DataInputStream.h" +#import "SetNSError.h" +#import "RegexKitLite.h" +#import "NSErrorCodes.h" +#import "Streams.h" + +#define HEADER_LENGTH (8) + +@interface Tree (internal) +- (BOOL)readHeader:(id )is error:(NSError **)error; +@end + +@implementation Tree +@synthesize xattrsSHA1, xattrsSize, aclSHA1, uid, gid, mode, mtime_sec, mtime_nsec, flags, finderFlags, extendedFinderFlags, treeVersion, st_rdev; +@synthesize ctime_sec, ctime_nsec, createTime_sec, createTime_nsec, st_nlink, st_ino; + +- (id)init { + if (self = [super init]) { + nodes = [[NSMutableDictionary alloc] init]; + } + return self; +} +- (id)initWithBufferedInputStream:(id )is error:(NSError **)error { + if (self = [super init]) { + if (![self readHeader:is error:error]) { + [self release]; + return nil; + } + BOOL ret = [StringIO read:&xattrsSHA1 from:is error:error] + && [IntegerIO readUInt64:&xattrsSize from:is error:error] + &&[StringIO read:&aclSHA1 from:is error:error] + && [IntegerIO readInt32:&uid from:is error:error] + && [IntegerIO readInt32:&gid from:is error:error] + && [IntegerIO readInt32:&mode from:is error:error] + && [IntegerIO readInt64:&mtime_sec from:is error:error] + && [IntegerIO readInt64:&mtime_nsec from:is error:error] + && [IntegerIO readInt64:&flags from:is error:error] + && [IntegerIO readInt32:&finderFlags from:is error:error] + && [IntegerIO readInt32:&extendedFinderFlags from:is error:error] + && [IntegerIO readInt32:&st_dev from:is error:error] + && [IntegerIO readInt32:&st_ino from:is error:error] + && [IntegerIO readUInt32:&st_nlink from:is error:error] + && [IntegerIO readInt32:&st_rdev from:is error:error] + && [IntegerIO readInt64:&ctime_sec from:is error:error] + && [IntegerIO readInt64:&ctime_nsec from:is error:error] + && [IntegerIO readInt64:&st_blocks from:is error:error] + && [IntegerIO readUInt32:&st_blksize from:is error:error]; + [xattrsSHA1 retain]; + [aclSHA1 retain]; + + if (!ret) { + goto initError; + } + + unsigned int nodeCount; + if (![IntegerIO readUInt32:&nodeCount from:is error:error]) { + goto initError; + } + nodes = [[NSMutableDictionary alloc] init]; + for (unsigned int i = 0; i < nodeCount; i++) { + NSString *nodeName; + if (![StringIO read:&nodeName from:is error:error]) { + goto initError; + } + Node *node = [[Node alloc] initWithInputStream:is treeVersion:treeVersion error:error]; + if (!node) { + goto initError; + } + [nodes setObject:node forKey:nodeName]; + [node release]; + } + goto initDone; + initError: + [self release]; + self = nil; + } +initDone: + return self; +} +- (void)dealloc { + [xattrsSHA1 release]; + [aclSHA1 release]; + [nodes release]; + [super dealloc]; +} +- (NSArray *)childNodeNames { + return [nodes allKeys]; +} +- (Node *)childNodeWithName:(NSString *)name { + return [nodes objectForKey:name]; +} +- (BOOL)containsNodeNamed:(NSString *)name { + return [nodes objectForKey:name] != nil; +} +@end + +@implementation Tree (internal) +- (BOOL)readHeader:(id )is error:(NSError **)error { + unsigned char *headerBytes = [is readExactly:HEADER_LENGTH error:error]; + if (headerBytes == NULL) { + return NO; + } + NSString *header = [[[NSString alloc] initWithBytes:headerBytes length:HEADER_LENGTH encoding:NSASCIIStringEncoding] autorelease]; + NSRange versionRange = [header rangeOfRegex:@"^TreeV(\\d{3})$" capture:1]; + treeVersion = 0; + if (versionRange.location != NSNotFound) { + NSNumberFormatter *nf = [[NSNumberFormatter alloc] init]; + NSNumber *number = [nf numberFromString:[header substringWithRange:versionRange]]; + treeVersion = [number intValue]; + [nf release]; + } + if (treeVersion != CURRENT_TREE_VERSION) { + SETNSERROR(@"TreeErrorDomain", ERROR_INVALID_OBJECT_VERSION, @"invalid Tree header: %@", header); + return NO; + } + return YES; +} +@end diff --git a/XAttrSet.h b/XAttrSet.h new file mode 100644 index 0000000..31889a2 --- /dev/null +++ b/XAttrSet.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "Blob.h" +#import "BufferedInputStream.h" + +@interface XAttrSet : NSObject { + NSMutableDictionary *xattrs; +} +- (id)initWithPath:(NSString *)thePath error:(NSError **)error; +- (id)initWithBufferedInputStream:(id )is error:(NSError **)error; +- (NSUInteger)count; +- (NSArray *)names; +- (BOOL)applyToFile:(NSString *)path error:(NSError **)error; +@end diff --git a/XAttrSet.m b/XAttrSet.m new file mode 100644 index 0000000..3702aeb --- /dev/null +++ b/XAttrSet.m @@ -0,0 +1,190 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#import "XAttrSet.h" +#import "StringIO.h" +#import "DataIO.h" +#import "IntegerIO.h" +#import "Blob.h" +#import "DataInputStream.h" +#import "SetNSError.h" +#import "NSErrorCodes.h" +#import "Streams.h" + +#define HEADER_LENGTH (12) + +@interface XAttrSet (internal) +- (BOOL)loadFromPath:(NSString *)thePath error:(NSError **)error; +- (BOOL)loadFromInputStream:(id )is error:(NSError **)error; +@end + +@implementation XAttrSet +- (id)initWithPath:(NSString *)thePath error:(NSError **)error { + if (self = [super init]) { + xattrs = [[NSMutableDictionary alloc] init]; + if (![self loadFromPath:thePath error:error]) { + [self release]; + self = nil; + } + } + return self; +} +- (id)initWithBufferedInputStream:(id )is error:(NSError **)error { + if (self = [super init]) { + xattrs = [[NSMutableDictionary alloc] init]; + if (![self loadFromInputStream:is error:error]) { + [self release]; + self = nil; + } + } + return self; +} +- (void)dealloc { + [xattrs release]; + [super dealloc]; +} +- (NSUInteger)count { + return [xattrs count]; +} +- (NSArray *)names { + return [xattrs allKeys]; +} +- (BOOL)applyToFile:(NSString *)path error:(NSError **)error { + XAttrSet *current = [[[XAttrSet alloc] initWithPath:path error:error] autorelease]; + if (!current) { + return NO; + } + const char *pathChars = [path fileSystemRepresentation]; + for (NSString *name in [current names]) { + if (removexattr(pathChars, [name UTF8String], XATTR_NOFOLLOW) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"removexattr: %s", strerror(errno)); + return NO; + } + } + for (NSString *key in [xattrs allKeys]) { + NSData *value = [xattrs objectForKey:key]; + if (setxattr(pathChars, + [key UTF8String], + [value bytes], + [value length], + 0, + XATTR_NOFOLLOW) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"setxattr: %s", strerror(errno)); + return NO; + } + } + return YES; +} +@end + +@implementation XAttrSet (internal) +- (BOOL)loadFromPath:(NSString *)thePath error:(NSError **)error { + NSDictionary *attribs = [[NSFileManager defaultManager] attributesOfItemAtPath:thePath error:error]; + if (attribs == nil) { + return NO; + } + NSString *fileType = [attribs objectForKey:NSFileType]; + if (![fileType isEqualToString:NSFileTypeSocket] + && ![fileType isEqualToString:NSFileTypeBlockSpecial] + && ![fileType isEqualToString:NSFileTypeCharacterSpecial] + && ![fileType isEqualToString:NSFileTypeUnknown]) { + const char *path = [thePath fileSystemRepresentation]; + ssize_t xattrsize = listxattr(path, NULL, 0, XATTR_NOFOLLOW); + if (xattrsize == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s", strerror(errno)); + return NO; + } + if (xattrsize > 0) { + char *xattrbuf = (char *)malloc(xattrsize); + xattrsize = listxattr(path, xattrbuf, xattrsize, XATTR_NOFOLLOW); + if (xattrsize == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s", strerror(errno)); + free(xattrbuf); + return NO; + } + for (char *name = xattrbuf; name < (xattrbuf + xattrsize); name += strlen(name) + 1) { + NSString *theName = [NSString stringWithUTF8String:name]; + ssize_t valuesize = getxattr(path, name, NULL, 0, 0, XATTR_NOFOLLOW); + NSData *xattrData = nil; + if (valuesize == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"Error reading extended attribute %s: %s", name, strerror(errno)); + free(xattrbuf); + return NO; + } + if (valuesize > 0) { + void *value = malloc(valuesize); + if (getxattr(path, name, value, valuesize, 0, XATTR_NOFOLLOW) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"getxattr: %s", strerror(errno)); + free(value); + free(xattrbuf); + return NO; + } + xattrData = [NSData dataWithBytes:value length:valuesize]; + free(value); + } else { + xattrData = [NSData data]; + } + [xattrs setObject:xattrData forKey:theName]; + } + free(xattrbuf); + } + } + return YES; +} +- (BOOL)loadFromInputStream:(id )is error:(NSError **)error { + unsigned char *headerBytes = [is readExactly:HEADER_LENGTH error:error]; + if (headerBytes == NULL) { + return NO; + } + if (strncmp((const char *)headerBytes, "XAttrSetV002", HEADER_LENGTH)) { + SETNSERROR(@"XAttrSetErrorDomain", ERROR_INVALID_OBJECT_VERSION, @"invalid XAttrSet header"); + return NO; + } + uint64_t count; + if (![IntegerIO readUInt64:&count from:is error:error]) { + return NO; + } + for (uint64_t i = 0; i < count; i++) { + NSString *name; + if (![StringIO read:&name from:is error:error]) { + return NO; + } + NSData *value; + if (![DataIO read:&value from:is error:error]) { + return NO; + } + [xattrs setObject:value forKey:name]; + } + return YES; +} +@end diff --git a/arq_restore.m b/arq_restore.m new file mode 100644 index 0000000..aa75a78 --- /dev/null +++ b/arq_restore.m @@ -0,0 +1,69 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#import +#import "ArqRestoreCommand.h" +#import "ArqFolder.h" + +static void printUsage(const char *exeName) { + fprintf(stderr, "\t%s\n", exeName); + fprintf(stderr, "\t%s /s3bucket/computerUUID/folderUUID\n", exeName); +} +int main (int argc, const char * argv[]) { + setHSLogLevel(HSLOG_LEVEL_ERROR); + char *exePath = strdup(argv[0]); + char *exeName = basename(exePath); + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + ArqRestoreCommand *cmd = [[[ArqRestoreCommand alloc] init] autorelease]; + int ret = 0; + NSError *error = nil; + if (argc == 1) { + if (![cmd printArqFolders:&error]) { + NSLog(@"%@", [error localizedDescription]); + ret = 1; + } else { + printf("\nType %s to restore\n", exeName); + } + } else if (argc == 2) { + if (![cmd restorePath:[NSString stringWithUTF8String:argv[1]] error:&error]) { + NSLog(@"%@", [error localizedDescription]); + ret = 1; + } + } else { + printUsage(exeName); + ret = 1; + } + [pool drain]; + free(exePath); + return ret; +} diff --git a/arq_restore.xcodeproj/.gitignore b/arq_restore.xcodeproj/.gitignore new file mode 100644 index 0000000..f5aa50f --- /dev/null +++ b/arq_restore.xcodeproj/.gitignore @@ -0,0 +1,2 @@ +stefan.mode1v3 +stefan.pbxuser diff --git a/arq_restore.xcodeproj/project.pbxproj b/arq_restore.xcodeproj/project.pbxproj new file mode 100644 index 0000000..398a092 --- /dev/null +++ b/arq_restore.xcodeproj/project.pbxproj @@ -0,0 +1,825 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 8DD76F9A0486AA7600D96B5E /* arq_restore.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* arq_restore.m */; settings = {ATTRIBUTES = (); }; }; + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; + F805B54D1160D3E6007EC01E /* ArqRestoreCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */; }; + F805B7211160D9C2007EC01E /* HSLog.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7201160D9C2007EC01E /* HSLog.m */; }; + F805B7301160DBE9007EC01E /* ArqFolder.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B72F1160DBE9007EC01E /* ArqFolder.m */; }; + F805B7531160DCFE007EC01E /* ArrayNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7421160DCFE007EC01E /* ArrayNode.m */; }; + F805B7541160DCFE007EC01E /* BooleanNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7441160DCFE007EC01E /* BooleanNode.m */; }; + F805B7551160DCFE007EC01E /* DictNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7461160DCFE007EC01E /* DictNode.m */; }; + F805B7561160DCFE007EC01E /* IntegerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7481160DCFE007EC01E /* IntegerNode.m */; }; + F805B7571160DCFE007EC01E /* RealNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B74C1160DCFE007EC01E /* RealNode.m */; }; + F805B7581160DCFE007EC01E /* StringNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B74E1160DCFE007EC01E /* StringNode.m */; }; + F805B7591160DCFE007EC01E /* XMLPListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7501160DCFE007EC01E /* XMLPListReader.m */; }; + F805B75A1160DCFE007EC01E /* XMLPListWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7521160DCFE007EC01E /* XMLPListWriter.m */; }; + F805B7821160DD60007EC01E /* HTTPConnection_S3.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7691160DD60007EC01E /* HTTPConnection_S3.m */; }; + F805B7831160DD60007EC01E /* NSError_S3.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B76B1160DD60007EC01E /* NSError_S3.m */; }; + F805B7841160DD60007EC01E /* PathReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B76D1160DD60007EC01E /* PathReceiver.m */; }; + F805B7851160DD60007EC01E /* S3AuthorizationParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B76F1160DD60007EC01E /* S3AuthorizationParameters.m */; }; + F805B7861160DD60007EC01E /* S3AuthorizationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7711160DD60007EC01E /* S3AuthorizationProvider.m */; }; + F805B7871160DD60007EC01E /* S3Lister.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7731160DD60007EC01E /* S3Lister.m */; }; + F805B7881160DD60007EC01E /* S3ObjectMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7751160DD60007EC01E /* S3ObjectMetadata.m */; }; + F805B7891160DD60007EC01E /* S3ObjectReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7771160DD60007EC01E /* S3ObjectReceiver.m */; }; + F805B78B1160DD60007EC01E /* S3Request.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B77C1160DD60007EC01E /* S3Request.m */; }; + F805B78C1160DD60007EC01E /* S3Service.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B77E1160DD60007EC01E /* S3Service.m */; }; + F805B78D1160DD60007EC01E /* S3Signature.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7801160DD60007EC01E /* S3Signature.m */; }; + F805B7A91160DEF2007EC01E /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7A81160DEF2007EC01E /* RegexKitLite.m */; }; + F805B7BA1160E3AF007EC01E /* Blob.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7B91160E3AF007EC01E /* Blob.m */; }; + F805B7D11160E445007EC01E /* HTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7CC1160E445007EC01E /* HTTPConnection.m */; }; + F805B7D21160E445007EC01E /* HTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7CE1160E445007EC01E /* HTTPRequest.m */; }; + F805B7D31160E445007EC01E /* HTTPResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7D01160E445007EC01E /* HTTPResponse.m */; }; + F805B7D81160E456007EC01E /* BlobACL.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7D71160E456007EC01E /* BlobACL.m */; }; + F805B7E11160E48B007EC01E /* ServerBlob.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7E01160E48B007EC01E /* ServerBlob.m */; }; + F805B7FB1160E73D007EC01E /* RFC2616DateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7FA1160E73D007EC01E /* RFC2616DateFormatter.m */; }; + F805B7FE1160E764007EC01E /* RFC822.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7FD1160E764007EC01E /* RFC822.m */; }; + F805B81B1160E838007EC01E /* InputStreams.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B81A1160E838007EC01E /* InputStreams.m */; }; + F805B8251160E857007EC01E /* ChunkedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8221160E857007EC01E /* ChunkedInputStream.m */; }; + F805B8261160E857007EC01E /* FixedLengthInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8241160E857007EC01E /* FixedLengthInputStream.m */; }; + F805B82B1160E861007EC01E /* FDInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8281160E861007EC01E /* FDInputStream.m */; }; + F805B82C1160E861007EC01E /* FDOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B82A1160E861007EC01E /* FDOutputStream.m */; }; + F805B82F1160E86E007EC01E /* DataInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B82E1160E86E007EC01E /* DataInputStream.m */; }; + F805B8321160E878007EC01E /* Writer.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8311160E878007EC01E /* Writer.m */; }; + F805B83B1160E8DD007EC01E /* NSData-InputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B83A1160E8DD007EC01E /* NSData-InputStream.m */; }; + F805B83F1160E900007EC01E /* StreamPairFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B83E1160E900007EC01E /* StreamPairFactory.m */; }; + F805B8421160E90F007EC01E /* Streams.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8411160E90F007EC01E /* Streams.m */; }; + F805B8531160E9B0007EC01E /* CFStreamPair.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8521160E9B0007EC01E /* CFStreamPair.m */; }; + F805B85A1160E9C9007EC01E /* CFStreamInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8571160E9C9007EC01E /* CFStreamInputStream.m */; }; + F805B85B1160E9C9007EC01E /* CFStreamOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8591160E9C9007EC01E /* CFStreamOutputStream.m */; }; + F805B8601160E9F0007EC01E /* DNS_SDErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B85F1160E9F0007EC01E /* DNS_SDErrors.m */; }; + F805B8651160EA15007EC01E /* NSXMLNode_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8641160EA15007EC01E /* NSXMLNode_extra.m */; }; + F805B86A1160EA83007EC01E /* NSData-Base64Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8691160EA83007EC01E /* NSData-Base64Extensions.m */; }; + F805B86F1160EAC1007EC01E /* DataInputStreamFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B86E1160EAC1007EC01E /* DataInputStreamFactory.m */; }; + F805B8891160EB39007EC01E /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8881160EB39007EC01E /* libcrypto.dylib */; }; + F805B88B1160EB3A007EC01E /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B88A1160EB39007EC01E /* libicucore.dylib */; }; + F805B88F1160EB45007EC01E /* libssl.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B88E1160EB45007EC01E /* libssl.dylib */; }; + F805B8931160EB4E007EC01E /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8921160EB4E007EC01E /* SystemConfiguration.framework */; }; + F805B8A11160EBAA007EC01E /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8A01160EBAA007EC01E /* CoreFoundation.framework */; }; + F805B8C21160EC41007EC01E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8C11160EC41007EC01E /* Security.framework */; }; + F805B8CE1160ECD7007EC01E /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8CD1160ECD7007EC01E /* CoreServices.framework */; }; + F8D678001160F26A00CC270E /* Restorer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D677FF1160F26A00CC270E /* Restorer.m */; }; + F8D678171160F4E300CC270E /* S3Fark.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678141160F4E300CC270E /* S3Fark.m */; }; + F8D678181160F4E300CC270E /* S3Repo.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678161160F4E300CC270E /* S3Repo.m */; }; + F8D6781D1160F4FD00CC270E /* SHA1Hash.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6781C1160F4FD00CC270E /* SHA1Hash.m */; }; + F8D6782D1160F5D000CC270E /* PackSetSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6782A1160F5D000CC270E /* PackSetSet.m */; }; + F8D6782E1160F5D000CC270E /* PackSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6782C1160F5D000CC270E /* PackSet.m */; }; + F8D678331160F62E00CC270E /* ArqUserLibrary.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678321160F62E00CC270E /* ArqUserLibrary.m */; }; + F8D6783C1160F70100CC270E /* PackIndexEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6783B1160F70100CC270E /* PackIndexEntry.m */; }; + F8D678451160F74A00CC270E /* DiskPack.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678421160F74A00CC270E /* DiskPack.m */; }; + F8D678461160F74A00CC270E /* DiskPackIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678441160F74A00CC270E /* DiskPackIndex.m */; }; + F8D6785F1160F7CF00CC270E /* Commit.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6785C1160F7CF00CC270E /* Commit.m */; }; + F8D678601160F7CF00CC270E /* Tree.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6785E1160F7CF00CC270E /* Tree.m */; }; + F8D678651160F7FE00CC270E /* NSData-Encrypt.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678641160F7FE00CC270E /* NSData-Encrypt.m */; }; + F8D6786A1160F81100CC270E /* OpenSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678691160F81100CC270E /* OpenSSL.m */; }; + F8D6786F1160F84600CC270E /* DecryptedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6786E1160F84600CC270E /* DecryptedInputStream.m */; }; + F8D678741160F85D00CC270E /* CryptInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678731160F85D00CC270E /* CryptInputStream.m */; }; + F8D678771160F86E00CC270E /* FileInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678761160F86E00CC270E /* FileInputStream.m */; }; + F8D678821160F8A000CC270E /* DataIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678791160F8A000CC270E /* DataIO.m */; }; + F8D678831160F8A000CC270E /* DateIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787B1160F8A000CC270E /* DateIO.m */; }; + F8D678841160F8A000CC270E /* DoubleIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787D1160F8A000CC270E /* DoubleIO.m */; }; + F8D678851160F8A000CC270E /* IntegerIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787F1160F8A000CC270E /* IntegerIO.m */; }; + F8D678861160F8A000CC270E /* StringIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678811160F8A000CC270E /* StringIO.m */; }; + F8D678891160F8CD00CC270E /* NSString_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678881160F8CD00CC270E /* NSString_extra.m */; }; + F8D6788C1160F8E500CC270E /* BinarySHA1.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6788B1160F8E500CC270E /* BinarySHA1.m */; }; + F8D6789A1160FA2A00CC270E /* NSFileManager_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678991160FA2A00CC270E /* NSFileManager_extra.m */; }; + F8D6789D1160FA3900CC270E /* FileOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6789C1160FA3900CC270E /* FileOutputStream.m */; }; + F8D678A01160FA4800CC270E /* FileInputStreamFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6789F1160FA4800CC270E /* FileInputStreamFactory.m */; }; + F8D678A51160FA5F00CC270E /* BooleanIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678A41160FA5F00CC270E /* BooleanIO.m */; }; + F8D678A81160FA6A00CC270E /* EncryptedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678A71160FA6A00CC270E /* EncryptedInputStream.m */; }; + F8D678AF1160FAD900CC270E /* CommitFailedFile.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678AE1160FAD900CC270E /* CommitFailedFile.m */; }; + F8D678B81160FB2100CC270E /* Node.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678B71160FB2100CC270E /* Node.m */; }; + F8D67CEB1161363A00CC270E /* FileAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67CEA1161363A00CC270E /* FileAttributes.m */; }; + F8D67CF21161366100CC270E /* XAttrSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67CF11161366100CC270E /* XAttrSet.m */; }; + F8D67D071161384100CC270E /* FileACL.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67D041161384100CC270E /* FileACL.m */; }; + F8D67D081161384100CC270E /* OSStatusDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67D061161384100CC270E /* OSStatusDescription.m */; }; + F8D67F701161443600CC270E /* RestoreNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67F6F1161443600CC270E /* RestoreNode.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 08FB7796FE84155DC02AAC07 /* arq_restore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = arq_restore.m; sourceTree = ""; }; + 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 32A70AAB03705E1F00C91783 /* arq_restore_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arq_restore_Prefix.pch; sourceTree = ""; }; + 8DD76FA10486AA7600D96B5E /* arq_restore */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = arq_restore; sourceTree = BUILT_PRODUCTS_DIR; }; + F805B54B1160D3E6007EC01E /* ArqRestoreCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqRestoreCommand.h; sourceTree = ""; }; + F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqRestoreCommand.m; sourceTree = ""; }; + F805B71F1160D9C2007EC01E /* HSLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSLog.h; sourceTree = ""; }; + F805B7201160D9C2007EC01E /* HSLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSLog.m; sourceTree = ""; }; + F805B72E1160DBE9007EC01E /* ArqFolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqFolder.h; sourceTree = ""; }; + F805B72F1160DBE9007EC01E /* ArqFolder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqFolder.m; sourceTree = ""; }; + F805B7411160DCFE007EC01E /* ArrayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayNode.h; sourceTree = ""; }; + F805B7421160DCFE007EC01E /* ArrayNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayNode.m; sourceTree = ""; }; + F805B7431160DCFE007EC01E /* BooleanNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BooleanNode.h; sourceTree = ""; }; + F805B7441160DCFE007EC01E /* BooleanNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BooleanNode.m; sourceTree = ""; }; + F805B7451160DCFE007EC01E /* DictNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DictNode.h; sourceTree = ""; }; + F805B7461160DCFE007EC01E /* DictNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DictNode.m; sourceTree = ""; }; + F805B7471160DCFE007EC01E /* IntegerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegerNode.h; sourceTree = ""; }; + F805B7481160DCFE007EC01E /* IntegerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegerNode.m; sourceTree = ""; }; + F805B7491160DCFE007EC01E /* PListNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PListNode.h; sourceTree = ""; }; + F805B74A1160DCFE007EC01E /* PListNodeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PListNodeType.h; sourceTree = ""; }; + F805B74B1160DCFE007EC01E /* RealNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RealNode.h; sourceTree = ""; }; + F805B74C1160DCFE007EC01E /* RealNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RealNode.m; sourceTree = ""; }; + F805B74D1160DCFE007EC01E /* StringNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringNode.h; sourceTree = ""; }; + F805B74E1160DCFE007EC01E /* StringNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StringNode.m; sourceTree = ""; }; + F805B74F1160DCFE007EC01E /* XMLPListReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLPListReader.h; sourceTree = ""; }; + F805B7501160DCFE007EC01E /* XMLPListReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLPListReader.m; sourceTree = ""; }; + F805B7511160DCFE007EC01E /* XMLPListWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLPListWriter.h; sourceTree = ""; }; + F805B7521160DCFE007EC01E /* XMLPListWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLPListWriter.m; sourceTree = ""; }; + F805B7681160DD60007EC01E /* HTTPConnection_S3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnection_S3.h; sourceTree = ""; }; + F805B7691160DD60007EC01E /* HTTPConnection_S3.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPConnection_S3.m; sourceTree = ""; }; + F805B76A1160DD60007EC01E /* NSError_S3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSError_S3.h; sourceTree = ""; }; + F805B76B1160DD60007EC01E /* NSError_S3.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSError_S3.m; sourceTree = ""; }; + F805B76C1160DD60007EC01E /* PathReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PathReceiver.h; sourceTree = ""; }; + F805B76D1160DD60007EC01E /* PathReceiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PathReceiver.m; sourceTree = ""; }; + F805B76E1160DD60007EC01E /* S3AuthorizationParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3AuthorizationParameters.h; sourceTree = ""; }; + F805B76F1160DD60007EC01E /* S3AuthorizationParameters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3AuthorizationParameters.m; sourceTree = ""; }; + F805B7701160DD60007EC01E /* S3AuthorizationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3AuthorizationProvider.h; sourceTree = ""; }; + F805B7711160DD60007EC01E /* S3AuthorizationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3AuthorizationProvider.m; sourceTree = ""; }; + F805B7721160DD60007EC01E /* S3Lister.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Lister.h; sourceTree = ""; }; + F805B7731160DD60007EC01E /* S3Lister.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Lister.m; sourceTree = ""; }; + F805B7741160DD60007EC01E /* S3ObjectMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3ObjectMetadata.h; sourceTree = ""; }; + F805B7751160DD60007EC01E /* S3ObjectMetadata.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3ObjectMetadata.m; sourceTree = ""; }; + F805B7761160DD60007EC01E /* S3ObjectReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3ObjectReceiver.h; sourceTree = ""; }; + F805B7771160DD60007EC01E /* S3ObjectReceiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3ObjectReceiver.m; sourceTree = ""; }; + F805B77A1160DD60007EC01E /* S3Receiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Receiver.h; sourceTree = ""; }; + F805B77B1160DD60007EC01E /* S3Request.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Request.h; sourceTree = ""; }; + F805B77C1160DD60007EC01E /* S3Request.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Request.m; sourceTree = ""; }; + F805B77D1160DD60007EC01E /* S3Service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Service.h; sourceTree = ""; }; + F805B77E1160DD60007EC01E /* S3Service.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Service.m; sourceTree = ""; }; + F805B77F1160DD60007EC01E /* S3Signature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Signature.h; sourceTree = ""; }; + F805B7801160DD60007EC01E /* S3Signature.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Signature.m; sourceTree = ""; }; + F805B7A71160DEF2007EC01E /* RegexKitLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegexKitLite.h; sourceTree = ""; }; + F805B7A81160DEF2007EC01E /* RegexKitLite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegexKitLite.m; sourceTree = ""; }; + F805B7B81160E3AF007EC01E /* Blob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Blob.h; sourceTree = ""; }; + F805B7B91160E3AF007EC01E /* Blob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Blob.m; sourceTree = ""; }; + F805B7CA1160E445007EC01E /* HTTP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTP.h; sourceTree = ""; }; + F805B7CB1160E445007EC01E /* HTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnection.h; sourceTree = ""; }; + F805B7CC1160E445007EC01E /* HTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPConnection.m; sourceTree = ""; }; + F805B7CD1160E445007EC01E /* HTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRequest.h; sourceTree = ""; }; + F805B7CE1160E445007EC01E /* HTTPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPRequest.m; sourceTree = ""; }; + F805B7CF1160E445007EC01E /* HTTPResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPResponse.h; sourceTree = ""; }; + F805B7D01160E445007EC01E /* HTTPResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPResponse.m; sourceTree = ""; }; + F805B7D61160E456007EC01E /* BlobACL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobACL.h; sourceTree = ""; }; + F805B7D71160E456007EC01E /* BlobACL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlobACL.m; sourceTree = ""; }; + F805B7DE1160E48B007EC01E /* NSErrorCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSErrorCodes.h; sourceTree = ""; }; + F805B7DF1160E48B007EC01E /* ServerBlob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServerBlob.h; sourceTree = ""; }; + F805B7E01160E48B007EC01E /* ServerBlob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServerBlob.m; sourceTree = ""; }; + F805B7F91160E73D007EC01E /* RFC2616DateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFC2616DateFormatter.h; sourceTree = ""; }; + F805B7FA1160E73D007EC01E /* RFC2616DateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFC2616DateFormatter.m; sourceTree = ""; }; + F805B7FC1160E764007EC01E /* RFC822.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFC822.h; sourceTree = ""; }; + F805B7FD1160E764007EC01E /* RFC822.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFC822.m; sourceTree = ""; }; + F805B80B1160E7C3007EC01E /* InputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputStream.h; sourceTree = ""; }; + F805B8191160E838007EC01E /* InputStreams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputStreams.h; sourceTree = ""; }; + F805B81A1160E838007EC01E /* InputStreams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InputStreams.m; sourceTree = ""; }; + F805B8211160E857007EC01E /* ChunkedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChunkedInputStream.h; sourceTree = ""; }; + F805B8221160E857007EC01E /* ChunkedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChunkedInputStream.m; sourceTree = ""; }; + F805B8231160E857007EC01E /* FixedLengthInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FixedLengthInputStream.h; sourceTree = ""; }; + F805B8241160E857007EC01E /* FixedLengthInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FixedLengthInputStream.m; sourceTree = ""; }; + F805B8271160E861007EC01E /* FDInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDInputStream.h; sourceTree = ""; }; + F805B8281160E861007EC01E /* FDInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDInputStream.m; sourceTree = ""; }; + F805B8291160E861007EC01E /* FDOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDOutputStream.h; sourceTree = ""; }; + F805B82A1160E861007EC01E /* FDOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDOutputStream.m; sourceTree = ""; }; + F805B82D1160E86E007EC01E /* DataInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataInputStream.h; sourceTree = ""; }; + F805B82E1160E86E007EC01E /* DataInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataInputStream.m; sourceTree = ""; }; + F805B8301160E878007EC01E /* Writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Writer.h; sourceTree = ""; }; + F805B8311160E878007EC01E /* Writer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Writer.m; sourceTree = ""; }; + F805B8331160E882007EC01E /* BufferedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BufferedInputStream.h; sourceTree = ""; }; + F805B8361160E8A0007EC01E /* OutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OutputStream.h; sourceTree = ""; }; + F805B8391160E8DD007EC01E /* NSData-InputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-InputStream.h"; sourceTree = ""; }; + F805B83A1160E8DD007EC01E /* NSData-InputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-InputStream.m"; sourceTree = ""; }; + F805B83C1160E900007EC01E /* StreamPair.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamPair.h; sourceTree = ""; }; + F805B83D1160E900007EC01E /* StreamPairFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamPairFactory.h; sourceTree = ""; }; + F805B83E1160E900007EC01E /* StreamPairFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamPairFactory.m; sourceTree = ""; }; + F805B8401160E90F007EC01E /* Streams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Streams.h; sourceTree = ""; }; + F805B8411160E90F007EC01E /* Streams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Streams.m; sourceTree = ""; }; + F805B8511160E9B0007EC01E /* CFStreamPair.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFStreamPair.h; sourceTree = ""; }; + F805B8521160E9B0007EC01E /* CFStreamPair.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFStreamPair.m; sourceTree = ""; }; + F805B8561160E9C9007EC01E /* CFStreamInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFStreamInputStream.h; sourceTree = ""; }; + F805B8571160E9C9007EC01E /* CFStreamInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFStreamInputStream.m; sourceTree = ""; }; + F805B8581160E9C9007EC01E /* CFStreamOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFStreamOutputStream.h; sourceTree = ""; }; + F805B8591160E9C9007EC01E /* CFStreamOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFStreamOutputStream.m; sourceTree = ""; }; + F805B85E1160E9F0007EC01E /* DNS_SDErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS_SDErrors.h; sourceTree = ""; }; + F805B85F1160E9F0007EC01E /* DNS_SDErrors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DNS_SDErrors.m; sourceTree = ""; }; + F805B8631160EA15007EC01E /* NSXMLNode_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSXMLNode_extra.h; sourceTree = ""; }; + F805B8641160EA15007EC01E /* NSXMLNode_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSXMLNode_extra.m; sourceTree = ""; }; + F805B8661160EA36007EC01E /* InputStreamFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputStreamFactory.h; sourceTree = ""; }; + F805B8681160EA83007EC01E /* NSData-Base64Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-Base64Extensions.h"; sourceTree = ""; }; + F805B8691160EA83007EC01E /* NSData-Base64Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-Base64Extensions.m"; sourceTree = ""; }; + F805B86D1160EAC1007EC01E /* DataInputStreamFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataInputStreamFactory.h; sourceTree = ""; }; + F805B86E1160EAC1007EC01E /* DataInputStreamFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataInputStreamFactory.m; sourceTree = ""; }; + F805B8881160EB39007EC01E /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; }; + F805B88A1160EB39007EC01E /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; + F805B88E1160EB45007EC01E /* libssl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssl.dylib; path = usr/lib/libssl.dylib; sourceTree = SDKROOT; }; + F805B8921160EB4E007EC01E /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + F805B8981160EB71007EC01E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + F805B8A01160EBAA007EC01E /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + F805B8BB1160EC38007EC01E /* SecurityFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SecurityFoundation.framework; path = System/Library/Frameworks/SecurityFoundation.framework; sourceTree = SDKROOT; }; + F805B8C11160EC41007EC01E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + F805B8C51160EC4E007EC01E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + F805B8CD1160ECD7007EC01E /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; + F8D6763E1160F22800CC270E /* SetNSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SetNSError.h; sourceTree = ""; }; + F8D677FE1160F26A00CC270E /* Restorer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Restorer.h; sourceTree = ""; }; + F8D677FF1160F26A00CC270E /* Restorer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Restorer.m; sourceTree = ""; }; + F8D678131160F4E300CC270E /* S3Fark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Fark.h; sourceTree = ""; }; + F8D678141160F4E300CC270E /* S3Fark.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Fark.m; sourceTree = ""; }; + F8D678151160F4E300CC270E /* S3Repo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Repo.h; sourceTree = ""; }; + F8D678161160F4E300CC270E /* S3Repo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Repo.m; sourceTree = ""; }; + F8D6781B1160F4FD00CC270E /* SHA1Hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHA1Hash.h; sourceTree = ""; }; + F8D6781C1160F4FD00CC270E /* SHA1Hash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SHA1Hash.m; sourceTree = ""; }; + F8D678291160F5D000CC270E /* PackSetSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackSetSet.h; sourceTree = ""; }; + F8D6782A1160F5D000CC270E /* PackSetSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackSetSet.m; sourceTree = ""; }; + F8D6782B1160F5D000CC270E /* PackSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackSet.h; sourceTree = ""; }; + F8D6782C1160F5D000CC270E /* PackSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackSet.m; sourceTree = ""; }; + F8D678311160F62E00CC270E /* ArqUserLibrary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqUserLibrary.h; sourceTree = ""; }; + F8D678321160F62E00CC270E /* ArqUserLibrary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqUserLibrary.m; sourceTree = ""; }; + F8D6783A1160F70100CC270E /* PackIndexEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackIndexEntry.h; sourceTree = ""; }; + F8D6783B1160F70100CC270E /* PackIndexEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackIndexEntry.m; sourceTree = ""; }; + F8D678411160F74A00CC270E /* DiskPack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiskPack.h; sourceTree = ""; }; + F8D678421160F74A00CC270E /* DiskPack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiskPack.m; sourceTree = ""; }; + F8D678431160F74A00CC270E /* DiskPackIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiskPackIndex.h; sourceTree = ""; }; + F8D678441160F74A00CC270E /* DiskPackIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiskPackIndex.m; sourceTree = ""; }; + F8D6785B1160F7CE00CC270E /* Commit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Commit.h; sourceTree = ""; }; + F8D6785C1160F7CF00CC270E /* Commit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Commit.m; sourceTree = ""; }; + F8D6785D1160F7CF00CC270E /* Tree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tree.h; sourceTree = ""; }; + F8D6785E1160F7CF00CC270E /* Tree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Tree.m; sourceTree = ""; }; + F8D678631160F7FE00CC270E /* NSData-Encrypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-Encrypt.h"; sourceTree = ""; }; + F8D678641160F7FE00CC270E /* NSData-Encrypt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-Encrypt.m"; sourceTree = ""; }; + F8D678681160F81100CC270E /* OpenSSL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenSSL.h; sourceTree = ""; }; + F8D678691160F81100CC270E /* OpenSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpenSSL.m; sourceTree = ""; }; + F8D6786D1160F84600CC270E /* DecryptedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DecryptedInputStream.h; sourceTree = ""; }; + F8D6786E1160F84600CC270E /* DecryptedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DecryptedInputStream.m; sourceTree = ""; }; + F8D678721160F85D00CC270E /* CryptInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptInputStream.h; sourceTree = ""; }; + F8D678731160F85D00CC270E /* CryptInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptInputStream.m; sourceTree = ""; }; + F8D678751160F86E00CC270E /* FileInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileInputStream.h; sourceTree = ""; }; + F8D678761160F86E00CC270E /* FileInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileInputStream.m; sourceTree = ""; }; + F8D678781160F8A000CC270E /* DataIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataIO.h; sourceTree = ""; }; + F8D678791160F8A000CC270E /* DataIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataIO.m; sourceTree = ""; }; + F8D6787A1160F8A000CC270E /* DateIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateIO.h; sourceTree = ""; }; + F8D6787B1160F8A000CC270E /* DateIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateIO.m; sourceTree = ""; }; + F8D6787C1160F8A000CC270E /* DoubleIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoubleIO.h; sourceTree = ""; }; + F8D6787D1160F8A000CC270E /* DoubleIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoubleIO.m; sourceTree = ""; }; + F8D6787E1160F8A000CC270E /* IntegerIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegerIO.h; sourceTree = ""; }; + F8D6787F1160F8A000CC270E /* IntegerIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegerIO.m; sourceTree = ""; }; + F8D678801160F8A000CC270E /* StringIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringIO.h; sourceTree = ""; }; + F8D678811160F8A000CC270E /* StringIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StringIO.m; sourceTree = ""; }; + F8D678871160F8CD00CC270E /* NSString_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSString_extra.h; sourceTree = ""; }; + F8D678881160F8CD00CC270E /* NSString_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSString_extra.m; sourceTree = ""; }; + F8D6788A1160F8E500CC270E /* BinarySHA1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BinarySHA1.h; sourceTree = ""; }; + F8D6788B1160F8E500CC270E /* BinarySHA1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BinarySHA1.m; sourceTree = ""; }; + F8D678981160FA2A00CC270E /* NSFileManager_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSFileManager_extra.h; sourceTree = ""; }; + F8D678991160FA2A00CC270E /* NSFileManager_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSFileManager_extra.m; sourceTree = ""; }; + F8D6789B1160FA3900CC270E /* FileOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileOutputStream.h; sourceTree = ""; }; + F8D6789C1160FA3900CC270E /* FileOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileOutputStream.m; sourceTree = ""; }; + F8D6789E1160FA4800CC270E /* FileInputStreamFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileInputStreamFactory.h; sourceTree = ""; }; + F8D6789F1160FA4800CC270E /* FileInputStreamFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileInputStreamFactory.m; sourceTree = ""; }; + F8D678A31160FA5F00CC270E /* BooleanIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BooleanIO.h; sourceTree = ""; }; + F8D678A41160FA5F00CC270E /* BooleanIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BooleanIO.m; sourceTree = ""; }; + F8D678A61160FA6A00CC270E /* EncryptedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EncryptedInputStream.h; sourceTree = ""; }; + F8D678A71160FA6A00CC270E /* EncryptedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EncryptedInputStream.m; sourceTree = ""; }; + F8D678AD1160FAD900CC270E /* CommitFailedFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommitFailedFile.h; sourceTree = ""; }; + F8D678AE1160FAD900CC270E /* CommitFailedFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommitFailedFile.m; sourceTree = ""; }; + F8D678B61160FB2100CC270E /* Node.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Node.h; sourceTree = ""; }; + F8D678B71160FB2100CC270E /* Node.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Node.m; sourceTree = ""; }; + F8D67CE91161363A00CC270E /* FileAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileAttributes.h; sourceTree = ""; }; + F8D67CEA1161363A00CC270E /* FileAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileAttributes.m; sourceTree = ""; }; + F8D67CF01161366100CC270E /* XAttrSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XAttrSet.h; sourceTree = ""; }; + F8D67CF11161366100CC270E /* XAttrSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XAttrSet.m; sourceTree = ""; }; + F8D67D031161384100CC270E /* FileACL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileACL.h; sourceTree = ""; }; + F8D67D041161384100CC270E /* FileACL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileACL.m; sourceTree = ""; }; + F8D67D051161384100CC270E /* OSStatusDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSStatusDescription.h; sourceTree = ""; }; + F8D67D061161384100CC270E /* OSStatusDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSStatusDescription.m; sourceTree = ""; }; + F8D67F6E1161443600CC270E /* RestoreNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RestoreNode.h; sourceTree = ""; }; + F8D67F6F1161443600CC270E /* RestoreNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RestoreNode.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, + F805B8891160EB39007EC01E /* libcrypto.dylib in Frameworks */, + F805B88B1160EB3A007EC01E /* libicucore.dylib in Frameworks */, + F805B88F1160EB45007EC01E /* libssl.dylib in Frameworks */, + F805B8931160EB4E007EC01E /* SystemConfiguration.framework in Frameworks */, + F805B8A11160EBAA007EC01E /* CoreFoundation.framework in Frameworks */, + F805B8C21160EC41007EC01E /* Security.framework in Frameworks */, + F805B8CE1160ECD7007EC01E /* CoreServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* arq_restore */ = { + isa = PBXGroup; + children = ( + 08FB7795FE84155DC02AAC07 /* Source */, + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = arq_restore; + sourceTree = ""; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + F805B8671160EA7C007EC01E /* crypto */, + F805B7A61160DEF2007EC01E /* shared */, + F805B7401160DCFE007EC01E /* plist */, + F805B8081160E7A1007EC01E /* io */, + F805B7C91160E445007EC01E /* http */, + F805B7651160DD60007EC01E /* s3 */, + 32A70AAB03705E1F00C91783 /* arq_restore_Prefix.pch */, + 08FB7796FE84155DC02AAC07 /* arq_restore.m */, + F8D678311160F62E00CC270E /* ArqUserLibrary.h */, + F8D678321160F62E00CC270E /* ArqUserLibrary.m */, + F805B54B1160D3E6007EC01E /* ArqRestoreCommand.h */, + F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */, + F805B72E1160DBE9007EC01E /* ArqFolder.h */, + F805B72F1160DBE9007EC01E /* ArqFolder.m */, + F8D6785B1160F7CE00CC270E /* Commit.h */, + F8D6785C1160F7CF00CC270E /* Commit.m */, + F8D678AD1160FAD900CC270E /* CommitFailedFile.h */, + F8D678AE1160FAD900CC270E /* CommitFailedFile.m */, + F8D678411160F74A00CC270E /* DiskPack.h */, + F8D678421160F74A00CC270E /* DiskPack.m */, + F8D678431160F74A00CC270E /* DiskPackIndex.h */, + F8D678441160F74A00CC270E /* DiskPackIndex.m */, + F805B71F1160D9C2007EC01E /* HSLog.h */, + F805B7201160D9C2007EC01E /* HSLog.m */, + F8D678B61160FB2100CC270E /* Node.h */, + F8D678B71160FB2100CC270E /* Node.m */, + F8D6783A1160F70100CC270E /* PackIndexEntry.h */, + F8D6783B1160F70100CC270E /* PackIndexEntry.m */, + F8D678291160F5D000CC270E /* PackSetSet.h */, + F8D6782A1160F5D000CC270E /* PackSetSet.m */, + F8D6782B1160F5D000CC270E /* PackSet.h */, + F8D6782C1160F5D000CC270E /* PackSet.m */, + F8D677FE1160F26A00CC270E /* Restorer.h */, + F8D677FF1160F26A00CC270E /* Restorer.m */, + F8D67F6E1161443600CC270E /* RestoreNode.h */, + F8D67F6F1161443600CC270E /* RestoreNode.m */, + F8D678131160F4E300CC270E /* S3Fark.h */, + F8D678141160F4E300CC270E /* S3Fark.m */, + F8D678151160F4E300CC270E /* S3Repo.h */, + F8D678161160F4E300CC270E /* S3Repo.m */, + F8D6785D1160F7CF00CC270E /* Tree.h */, + F8D6785E1160F7CF00CC270E /* Tree.m */, + F8D67CE91161363A00CC270E /* FileAttributes.h */, + F8D67CEA1161363A00CC270E /* FileAttributes.m */, + F8D67CF01161366100CC270E /* XAttrSet.h */, + F8D67CF11161366100CC270E /* XAttrSet.m */, + ); + name = Source; + sourceTree = ""; + }; + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 08FB779EFE84155DC02AAC07 /* Foundation.framework */, + F805B8921160EB4E007EC01E /* SystemConfiguration.framework */, + F805B8981160EB71007EC01E /* Security.framework */, + F805B8A01160EBAA007EC01E /* CoreFoundation.framework */, + F805B8BB1160EC38007EC01E /* SecurityFoundation.framework */, + F805B8C11160EC41007EC01E /* Security.framework */, + F805B8C51160EC4E007EC01E /* Cocoa.framework */, + F805B8CD1160ECD7007EC01E /* CoreServices.framework */, + F805B8881160EB39007EC01E /* libcrypto.dylib */, + F805B88A1160EB39007EC01E /* libicucore.dylib */, + F805B88E1160EB45007EC01E /* libssl.dylib */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8DD76FA10486AA7600D96B5E /* arq_restore */, + ); + name = Products; + sourceTree = ""; + }; + F805B7401160DCFE007EC01E /* plist */ = { + isa = PBXGroup; + children = ( + F805B7411160DCFE007EC01E /* ArrayNode.h */, + F805B7421160DCFE007EC01E /* ArrayNode.m */, + F805B7431160DCFE007EC01E /* BooleanNode.h */, + F805B7441160DCFE007EC01E /* BooleanNode.m */, + F805B7451160DCFE007EC01E /* DictNode.h */, + F805B7461160DCFE007EC01E /* DictNode.m */, + F805B7471160DCFE007EC01E /* IntegerNode.h */, + F805B7481160DCFE007EC01E /* IntegerNode.m */, + F805B7491160DCFE007EC01E /* PListNode.h */, + F805B74A1160DCFE007EC01E /* PListNodeType.h */, + F805B74B1160DCFE007EC01E /* RealNode.h */, + F805B74C1160DCFE007EC01E /* RealNode.m */, + F805B74D1160DCFE007EC01E /* StringNode.h */, + F805B74E1160DCFE007EC01E /* StringNode.m */, + F805B74F1160DCFE007EC01E /* XMLPListReader.h */, + F805B7501160DCFE007EC01E /* XMLPListReader.m */, + F805B7511160DCFE007EC01E /* XMLPListWriter.h */, + F805B7521160DCFE007EC01E /* XMLPListWriter.m */, + ); + path = plist; + sourceTree = ""; + }; + F805B7651160DD60007EC01E /* s3 */ = { + isa = PBXGroup; + children = ( + F805B7681160DD60007EC01E /* HTTPConnection_S3.h */, + F805B7691160DD60007EC01E /* HTTPConnection_S3.m */, + F805B76A1160DD60007EC01E /* NSError_S3.h */, + F805B76B1160DD60007EC01E /* NSError_S3.m */, + F805B76C1160DD60007EC01E /* PathReceiver.h */, + F805B76D1160DD60007EC01E /* PathReceiver.m */, + F805B76E1160DD60007EC01E /* S3AuthorizationParameters.h */, + F805B76F1160DD60007EC01E /* S3AuthorizationParameters.m */, + F805B7701160DD60007EC01E /* S3AuthorizationProvider.h */, + F805B7711160DD60007EC01E /* S3AuthorizationProvider.m */, + F805B7721160DD60007EC01E /* S3Lister.h */, + F805B7731160DD60007EC01E /* S3Lister.m */, + F805B7741160DD60007EC01E /* S3ObjectMetadata.h */, + F805B7751160DD60007EC01E /* S3ObjectMetadata.m */, + F805B7761160DD60007EC01E /* S3ObjectReceiver.h */, + F805B7771160DD60007EC01E /* S3ObjectReceiver.m */, + F805B77A1160DD60007EC01E /* S3Receiver.h */, + F805B77B1160DD60007EC01E /* S3Request.h */, + F805B77C1160DD60007EC01E /* S3Request.m */, + F805B77D1160DD60007EC01E /* S3Service.h */, + F805B77E1160DD60007EC01E /* S3Service.m */, + F805B77F1160DD60007EC01E /* S3Signature.h */, + F805B7801160DD60007EC01E /* S3Signature.m */, + ); + path = s3; + sourceTree = ""; + }; + F805B7A61160DEF2007EC01E /* shared */ = { + isa = PBXGroup; + children = ( + F8D6788A1160F8E500CC270E /* BinarySHA1.h */, + F8D6788B1160F8E500CC270E /* BinarySHA1.m */, + F805B7D61160E456007EC01E /* BlobACL.h */, + F805B7D71160E456007EC01E /* BlobACL.m */, + F805B7B81160E3AF007EC01E /* Blob.h */, + F805B7B91160E3AF007EC01E /* Blob.m */, + F805B85E1160E9F0007EC01E /* DNS_SDErrors.h */, + F805B85F1160E9F0007EC01E /* DNS_SDErrors.m */, + F8D67D031161384100CC270E /* FileACL.h */, + F8D67D041161384100CC270E /* FileACL.m */, + F805B7DE1160E48B007EC01E /* NSErrorCodes.h */, + F8D678871160F8CD00CC270E /* NSString_extra.h */, + F8D678881160F8CD00CC270E /* NSString_extra.m */, + F805B8631160EA15007EC01E /* NSXMLNode_extra.h */, + F805B8641160EA15007EC01E /* NSXMLNode_extra.m */, + F8D67D051161384100CC270E /* OSStatusDescription.h */, + F8D67D061161384100CC270E /* OSStatusDescription.m */, + F805B7FC1160E764007EC01E /* RFC822.h */, + F805B7FD1160E764007EC01E /* RFC822.m */, + F805B7A71160DEF2007EC01E /* RegexKitLite.h */, + F805B7A81160DEF2007EC01E /* RegexKitLite.m */, + F805B7DF1160E48B007EC01E /* ServerBlob.h */, + F805B7E01160E48B007EC01E /* ServerBlob.m */, + F8D6763E1160F22800CC270E /* SetNSError.h */, + ); + path = shared; + sourceTree = ""; + }; + F805B7C91160E445007EC01E /* http */ = { + isa = PBXGroup; + children = ( + F805B7CA1160E445007EC01E /* HTTP.h */, + F805B7CB1160E445007EC01E /* HTTPConnection.h */, + F805B7CC1160E445007EC01E /* HTTPConnection.m */, + F805B7CD1160E445007EC01E /* HTTPRequest.h */, + F805B7CE1160E445007EC01E /* HTTPRequest.m */, + F805B7CF1160E445007EC01E /* HTTPResponse.h */, + F805B7D01160E445007EC01E /* HTTPResponse.m */, + F805B7F91160E73D007EC01E /* RFC2616DateFormatter.h */, + F805B7FA1160E73D007EC01E /* RFC2616DateFormatter.m */, + ); + path = http; + sourceTree = ""; + }; + F805B8081160E7A1007EC01E /* io */ = { + isa = PBXGroup; + children = ( + F8D678A31160FA5F00CC270E /* BooleanIO.h */, + F8D678A41160FA5F00CC270E /* BooleanIO.m */, + F805B8331160E882007EC01E /* BufferedInputStream.h */, + F805B8561160E9C9007EC01E /* CFStreamInputStream.h */, + F805B8571160E9C9007EC01E /* CFStreamInputStream.m */, + F805B8581160E9C9007EC01E /* CFStreamOutputStream.h */, + F805B8591160E9C9007EC01E /* CFStreamOutputStream.m */, + F805B8511160E9B0007EC01E /* CFStreamPair.h */, + F805B8521160E9B0007EC01E /* CFStreamPair.m */, + F805B8211160E857007EC01E /* ChunkedInputStream.h */, + F805B8221160E857007EC01E /* ChunkedInputStream.m */, + F8D678721160F85D00CC270E /* CryptInputStream.h */, + F8D678731160F85D00CC270E /* CryptInputStream.m */, + F8D678781160F8A000CC270E /* DataIO.h */, + F8D678791160F8A000CC270E /* DataIO.m */, + F805B86D1160EAC1007EC01E /* DataInputStreamFactory.h */, + F805B86E1160EAC1007EC01E /* DataInputStreamFactory.m */, + F805B82D1160E86E007EC01E /* DataInputStream.h */, + F805B82E1160E86E007EC01E /* DataInputStream.m */, + F8D6787A1160F8A000CC270E /* DateIO.h */, + F8D6787B1160F8A000CC270E /* DateIO.m */, + F8D6786D1160F84600CC270E /* DecryptedInputStream.h */, + F8D6786E1160F84600CC270E /* DecryptedInputStream.m */, + F8D6787C1160F8A000CC270E /* DoubleIO.h */, + F8D6787D1160F8A000CC270E /* DoubleIO.m */, + F8D678A61160FA6A00CC270E /* EncryptedInputStream.h */, + F8D678A71160FA6A00CC270E /* EncryptedInputStream.m */, + F805B8271160E861007EC01E /* FDInputStream.h */, + F805B8281160E861007EC01E /* FDInputStream.m */, + F805B8291160E861007EC01E /* FDOutputStream.h */, + F805B82A1160E861007EC01E /* FDOutputStream.m */, + F8D678751160F86E00CC270E /* FileInputStream.h */, + F8D678761160F86E00CC270E /* FileInputStream.m */, + F8D6789E1160FA4800CC270E /* FileInputStreamFactory.h */, + F8D6789F1160FA4800CC270E /* FileInputStreamFactory.m */, + F8D6789B1160FA3900CC270E /* FileOutputStream.h */, + F8D6789C1160FA3900CC270E /* FileOutputStream.m */, + F805B8231160E857007EC01E /* FixedLengthInputStream.h */, + F805B8241160E857007EC01E /* FixedLengthInputStream.m */, + F805B8661160EA36007EC01E /* InputStreamFactory.h */, + F805B8191160E838007EC01E /* InputStreams.h */, + F805B81A1160E838007EC01E /* InputStreams.m */, + F805B80B1160E7C3007EC01E /* InputStream.h */, + F8D6787E1160F8A000CC270E /* IntegerIO.h */, + F8D6787F1160F8A000CC270E /* IntegerIO.m */, + F805B8391160E8DD007EC01E /* NSData-InputStream.h */, + F805B83A1160E8DD007EC01E /* NSData-InputStream.m */, + F8D678981160FA2A00CC270E /* NSFileManager_extra.h */, + F8D678991160FA2A00CC270E /* NSFileManager_extra.m */, + F805B8361160E8A0007EC01E /* OutputStream.h */, + F805B83C1160E900007EC01E /* StreamPair.h */, + F805B83D1160E900007EC01E /* StreamPairFactory.h */, + F805B83E1160E900007EC01E /* StreamPairFactory.m */, + F805B8401160E90F007EC01E /* Streams.h */, + F805B8411160E90F007EC01E /* Streams.m */, + F8D678801160F8A000CC270E /* StringIO.h */, + F8D678811160F8A000CC270E /* StringIO.m */, + F805B8301160E878007EC01E /* Writer.h */, + F805B8311160E878007EC01E /* Writer.m */, + ); + path = io; + sourceTree = ""; + }; + F805B8671160EA7C007EC01E /* crypto */ = { + isa = PBXGroup; + children = ( + F8D6781B1160F4FD00CC270E /* SHA1Hash.h */, + F8D6781C1160F4FD00CC270E /* SHA1Hash.m */, + F805B8681160EA83007EC01E /* NSData-Base64Extensions.h */, + F805B8691160EA83007EC01E /* NSData-Base64Extensions.m */, + F8D678631160F7FE00CC270E /* NSData-Encrypt.h */, + F8D678641160F7FE00CC270E /* NSData-Encrypt.m */, + F8D678681160F81100CC270E /* OpenSSL.h */, + F8D678691160F81100CC270E /* OpenSSL.m */, + ); + path = crypto; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F960486AA7600D96B5E /* arq_restore */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "arq_restore" */; + buildPhases = ( + 8DD76F990486AA7600D96B5E /* Sources */, + 8DD76F9B0486AA7600D96B5E /* Frameworks */, + 8DD76F9E0486AA7600D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = arq_restore; + productInstallPath = "$(HOME)/bin"; + productName = arq_restore; + productReference = 8DD76FA10486AA7600D96B5E /* arq_restore */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "arq_restore" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* arq_restore */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DD76F960486AA7600D96B5E /* arq_restore */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F990486AA7600D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD76F9A0486AA7600D96B5E /* arq_restore.m in Sources */, + F805B54D1160D3E6007EC01E /* ArqRestoreCommand.m in Sources */, + F805B7211160D9C2007EC01E /* HSLog.m in Sources */, + F805B7301160DBE9007EC01E /* ArqFolder.m in Sources */, + F805B7531160DCFE007EC01E /* ArrayNode.m in Sources */, + F805B7541160DCFE007EC01E /* BooleanNode.m in Sources */, + F805B7551160DCFE007EC01E /* DictNode.m in Sources */, + F805B7561160DCFE007EC01E /* IntegerNode.m in Sources */, + F805B7571160DCFE007EC01E /* RealNode.m in Sources */, + F805B7581160DCFE007EC01E /* StringNode.m in Sources */, + F805B7591160DCFE007EC01E /* XMLPListReader.m in Sources */, + F805B75A1160DCFE007EC01E /* XMLPListWriter.m in Sources */, + F805B7821160DD60007EC01E /* HTTPConnection_S3.m in Sources */, + F805B7831160DD60007EC01E /* NSError_S3.m in Sources */, + F805B7841160DD60007EC01E /* PathReceiver.m in Sources */, + F805B7851160DD60007EC01E /* S3AuthorizationParameters.m in Sources */, + F805B7861160DD60007EC01E /* S3AuthorizationProvider.m in Sources */, + F805B7871160DD60007EC01E /* S3Lister.m in Sources */, + F805B7881160DD60007EC01E /* S3ObjectMetadata.m in Sources */, + F805B7891160DD60007EC01E /* S3ObjectReceiver.m in Sources */, + F805B78B1160DD60007EC01E /* S3Request.m in Sources */, + F805B78C1160DD60007EC01E /* S3Service.m in Sources */, + F805B78D1160DD60007EC01E /* S3Signature.m in Sources */, + F805B7A91160DEF2007EC01E /* RegexKitLite.m in Sources */, + F805B7BA1160E3AF007EC01E /* Blob.m in Sources */, + F805B7D11160E445007EC01E /* HTTPConnection.m in Sources */, + F805B7D21160E445007EC01E /* HTTPRequest.m in Sources */, + F805B7D31160E445007EC01E /* HTTPResponse.m in Sources */, + F805B7D81160E456007EC01E /* BlobACL.m in Sources */, + F805B7E11160E48B007EC01E /* ServerBlob.m in Sources */, + F805B7FB1160E73D007EC01E /* RFC2616DateFormatter.m in Sources */, + F805B7FE1160E764007EC01E /* RFC822.m in Sources */, + F805B81B1160E838007EC01E /* InputStreams.m in Sources */, + F805B8251160E857007EC01E /* ChunkedInputStream.m in Sources */, + F805B8261160E857007EC01E /* FixedLengthInputStream.m in Sources */, + F805B82B1160E861007EC01E /* FDInputStream.m in Sources */, + F805B82C1160E861007EC01E /* FDOutputStream.m in Sources */, + F805B82F1160E86E007EC01E /* DataInputStream.m in Sources */, + F805B8321160E878007EC01E /* Writer.m in Sources */, + F805B83B1160E8DD007EC01E /* NSData-InputStream.m in Sources */, + F805B83F1160E900007EC01E /* StreamPairFactory.m in Sources */, + F805B8421160E90F007EC01E /* Streams.m in Sources */, + F805B8531160E9B0007EC01E /* CFStreamPair.m in Sources */, + F805B85A1160E9C9007EC01E /* CFStreamInputStream.m in Sources */, + F805B85B1160E9C9007EC01E /* CFStreamOutputStream.m in Sources */, + F805B8601160E9F0007EC01E /* DNS_SDErrors.m in Sources */, + F805B8651160EA15007EC01E /* NSXMLNode_extra.m in Sources */, + F805B86A1160EA83007EC01E /* NSData-Base64Extensions.m in Sources */, + F805B86F1160EAC1007EC01E /* DataInputStreamFactory.m in Sources */, + F8D678001160F26A00CC270E /* Restorer.m in Sources */, + F8D678171160F4E300CC270E /* S3Fark.m in Sources */, + F8D678181160F4E300CC270E /* S3Repo.m in Sources */, + F8D6781D1160F4FD00CC270E /* SHA1Hash.m in Sources */, + F8D6782D1160F5D000CC270E /* PackSetSet.m in Sources */, + F8D6782E1160F5D000CC270E /* PackSet.m in Sources */, + F8D678331160F62E00CC270E /* ArqUserLibrary.m in Sources */, + F8D6783C1160F70100CC270E /* PackIndexEntry.m in Sources */, + F8D678451160F74A00CC270E /* DiskPack.m in Sources */, + F8D678461160F74A00CC270E /* DiskPackIndex.m in Sources */, + F8D6785F1160F7CF00CC270E /* Commit.m in Sources */, + F8D678601160F7CF00CC270E /* Tree.m in Sources */, + F8D678651160F7FE00CC270E /* NSData-Encrypt.m in Sources */, + F8D6786A1160F81100CC270E /* OpenSSL.m in Sources */, + F8D6786F1160F84600CC270E /* DecryptedInputStream.m in Sources */, + F8D678741160F85D00CC270E /* CryptInputStream.m in Sources */, + F8D678771160F86E00CC270E /* FileInputStream.m in Sources */, + F8D678821160F8A000CC270E /* DataIO.m in Sources */, + F8D678831160F8A000CC270E /* DateIO.m in Sources */, + F8D678841160F8A000CC270E /* DoubleIO.m in Sources */, + F8D678851160F8A000CC270E /* IntegerIO.m in Sources */, + F8D678861160F8A000CC270E /* StringIO.m in Sources */, + F8D678891160F8CD00CC270E /* NSString_extra.m in Sources */, + F8D6788C1160F8E500CC270E /* BinarySHA1.m in Sources */, + F8D6789A1160FA2A00CC270E /* NSFileManager_extra.m in Sources */, + F8D6789D1160FA3900CC270E /* FileOutputStream.m in Sources */, + F8D678A01160FA4800CC270E /* FileInputStreamFactory.m in Sources */, + F8D678A51160FA5F00CC270E /* BooleanIO.m in Sources */, + F8D678A81160FA6A00CC270E /* EncryptedInputStream.m in Sources */, + F8D678AF1160FAD900CC270E /* CommitFailedFile.m in Sources */, + F8D678B81160FB2100CC270E /* Node.m in Sources */, + F8D67CEB1161363A00CC270E /* FileAttributes.m in Sources */, + F8D67CF21161366100CC270E /* XAttrSet.m in Sources */, + F8D67D071161384100CC270E /* FileACL.m in Sources */, + F8D67D081161384100CC270E /* OSStatusDescription.m in Sources */, + F8D67F701161443600CC270E /* RestoreNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB927508733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = arq_restore_Prefix.pch; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = /usr/local/bin; + PRODUCT_NAME = arq_restore; + SDKROOT = macosx10.5; + }; + name = Debug; + }; + 1DEB927608733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = arq_restore_Prefix.pch; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = /usr/local/bin; + PRODUCT_NAME = arq_restore; + SDKROOT = macosx10.5; + }; + name = Release; + }; + 1DEB927908733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = macosx10.6; + }; + name = Debug; + }; + 1DEB927A08733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = macosx10.6; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "arq_restore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927508733DD40010E9CD /* Debug */, + 1DEB927608733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "arq_restore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927908733DD40010E9CD /* Debug */, + 1DEB927A08733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/arq_restore_Prefix.pch b/arq_restore_Prefix.pch new file mode 100644 index 0000000..75556a0 --- /dev/null +++ b/arq_restore_Prefix.pch @@ -0,0 +1,4 @@ +#ifdef __OBJC__ + #import + #import "HSLog.h" +#endif diff --git a/arq_restore_usage b/arq_restore_usage new file mode 100644 index 0000000..dbf760e --- /dev/null +++ b/arq_restore_usage @@ -0,0 +1,8 @@ +usage: +- set ARQ_ACCESS_KEY and ARQ_SECRET_KEY +- arq_restore + - /akiaiyuk3n3tme6l4hfa.com.haystacksoftware.arq.eu/1C493DC6-FB2C-4EEC-8356-838DABE3AE2C/3AA39F05-4C47-4CE5-839A-3A28255DD91E path=/Users/stefan +- set ARQ_ENCRYPTION_PASSWORD +- arq_restore 3AA39F05-4C47-4CE5-839A-3A28255DD91E + - restores 'stefan' folder to local directory, unless 'stefan' folder already exists + diff --git a/crypto/NSData-Base64Extensions.h b/crypto/NSData-Base64Extensions.h new file mode 100644 index 0000000..4691715 --- /dev/null +++ b/crypto/NSData-Base64Extensions.h @@ -0,0 +1,28 @@ +// Copyright (c) 2006 Dave Dribin (http://www.dribin.org/dave/) +// 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 + +@interface NSData (Base64) + +- (NSString *) encodeBase64; +- (NSString *) encodeBase64WithNewlines: (BOOL) encodeWithNewlines; + +@end diff --git a/crypto/NSData-Base64Extensions.m b/crypto/NSData-Base64Extensions.m new file mode 100644 index 0000000..434d0a2 --- /dev/null +++ b/crypto/NSData-Base64Extensions.m @@ -0,0 +1,57 @@ +// Copyright (c) 2006 Dave Dribin (http://www.dribin.org/dave/) +// 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 "NSData-Base64Extensions.h" +#include +#include + +@implementation NSData (Base64) + +- (NSString *) encodeBase64; +{ + return [self encodeBase64WithNewlines: YES]; +} + +- (NSString *) encodeBase64WithNewlines: (BOOL) encodeWithNewlines; +{ + // Create a memory buffer which will contain the Base64 encoded string + BIO * mem = BIO_new(BIO_s_mem()); + + // Push on a Base64 filter so that writing to the buffer encodes the data + BIO * b64 = BIO_new(BIO_f_base64()); + if (!encodeWithNewlines) + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + mem = BIO_push(b64, mem); + + // Encode all the data + BIO_write(mem, [self bytes], [self length]); + BIO_flush(mem); + + // Create a new string from the data in the memory buffer + char * base64Pointer; + long base64Length = BIO_get_mem_data(mem, &base64Pointer); + NSString *base64String = [[[NSString alloc] initWithCString:base64Pointer length:base64Length] autorelease]; + + // Clean up and go home + BIO_free_all(mem); + return base64String; +} + +@end diff --git a/crypto/NSData-Encrypt.h b/crypto/NSData-Encrypt.h new file mode 100644 index 0000000..176eafc --- /dev/null +++ b/crypto/NSData-Encrypt.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + +#define ARQ_DEFAULT_CIPHER_NAME @"aes256" + +@interface NSData (Encrypt) +- (NSData *)encryptWithCipher:(NSString *)cipherName key:(NSString *)key error:(NSError **)error; +- (NSData *)decryptWithCipher:(NSString *)cipherName key:(NSString *)key error:(NSError **)error; + +@end diff --git a/crypto/NSData-Encrypt.m b/crypto/NSData-Encrypt.m new file mode 100644 index 0000000..ea898c5 --- /dev/null +++ b/crypto/NSData-Encrypt.m @@ -0,0 +1,190 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + +#import "NSData-Encrypt.h" +#import "OpenSSL.h" +#import "SetNSError.h" + +@interface Crypter : NSObject { + NSData *data; + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX cipherContext; + unsigned char evp_key[EVP_MAX_KEY_LENGTH]; + unsigned char iv[EVP_MAX_IV_LENGTH]; +} +- (id)initWithCipher:(NSString *)cipherName key:(NSString *)key data:(NSData *)theData error:(NSError **)error; +- (NSData *)encrypt:(NSError **)error; +- (NSData *)decrypt:(NSError **)error; +@end + +@implementation Crypter +- (id)initWithCipher:(NSString *)cipherName key:(NSString *)key data:(NSData *)theData error:(NSError **)error { + if (self = [super init]) { + BOOL ret = NO; + do { + data = [theData retain]; + if ([data length] > 0) { + NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding]; + if ([keyData length] > EVP_MAX_KEY_LENGTH) { + SETNSERROR(@"NSDataEncryptErrorDomain", -1, @"encryption key must be less than or equal to %d bytes", EVP_MAX_KEY_LENGTH); + break; + } + if (![OpenSSL initializeSSL:error]) { + break; + } + cipher = EVP_get_cipherbyname([cipherName UTF8String]); + if (!cipher) { + SETNSERROR(@"NSDataEncryptErrorDomain", -1, @"failed to load %@ cipher: %@", cipherName, [OpenSSL errorMessage]); + break; + } + + evp_key[0] = 0; + EVP_BytesToKey(cipher, EVP_md5(), NULL, [keyData bytes], [keyData length], 1, evp_key, iv); + + EVP_CIPHER_CTX_init(&cipherContext); + } + ret = YES; + } while(0); + if (!ret) { + [self release]; + self = nil; + } + } + return self; +} +- (void)dealloc { + if ([data length] > 0) { + EVP_CIPHER_CTX_cleanup(&cipherContext); + } + [data release]; + [super dealloc]; +} +- (NSData *)encrypt:(NSError **)error { + if ([data length] == 0) { + return [NSData data]; + } + + if (!EVP_EncryptInit(&cipherContext, cipher, evp_key, iv)) { + SETNSERROR(@"NSDataEncryptErrorDomain", -1, @"EVP_EncryptInit: %@", [OpenSSL errorMessage]); + return nil; + } + EVP_CIPHER_CTX_set_key_length(&cipherContext, EVP_MAX_KEY_LENGTH); + + // Need room for data + cipher block size - 1. + unsigned char *outbuf = (unsigned char *)calloc([data length] + EVP_CIPHER_CTX_block_size(&cipherContext) - 1, sizeof(unsigned char)); + + int outlen; + if (!EVP_EncryptUpdate(&cipherContext, outbuf, &outlen, [data bytes], [data length])) { + SETNSERROR(@"NSDataEncryptErrorDomain", -1, @"EVP_EncryptUpdate: %@", [OpenSSL errorMessage]); + return nil; + } + int templen; + if (!EVP_EncryptFinal(&cipherContext, outbuf + outlen, &templen)) { + SETNSERROR(@"NSDataEncryptErrorDomain", -1, @"EVP_EncryptFinal: %@", [OpenSSL errorMessage]); + return nil; + } + outlen += templen; + NSData *ret = [[[NSData alloc] initWithBytes:outbuf length:outlen] autorelease]; + free(outbuf); + return ret; +} +- (NSData *)decrypt:(NSError **)error { + if ([data length] == 0) { + return [NSData data]; + } + + int inlen = [data length]; + unsigned char *input = (unsigned char *)[data bytes]; + + // Check for 8-byte salt in encrypted data and skip it. + if (inlen > 8+8 && strncmp((const char *)input, "Salted__", 8) == 0) { + input += 16; + inlen -= 16; + } + + if (!EVP_DecryptInit(&cipherContext, cipher, evp_key, iv)) { + SETNSERROR(@"NSDataDecryptErrorDomain", -1, @"EVP_DecryptInit: %@", [OpenSSL errorMessage]); + return nil; + } + EVP_CIPHER_CTX_set_key_length(&cipherContext, EVP_MAX_KEY_LENGTH); + + // The data buffer passed to EVP_DecryptUpdate() should have sufficient room for + // (input_length + cipher_block_size) bytes unless the cipher block size is 1 in which + // case input_length bytes is sufficient. + unsigned char *outbuf; + if(EVP_CIPHER_CTX_block_size(&cipherContext) > 1) { + outbuf = (unsigned char *)calloc(inlen + EVP_CIPHER_CTX_block_size(&cipherContext), sizeof(unsigned char)); + } else { + outbuf = (unsigned char *)calloc(inlen, sizeof(unsigned char)); + } + + int outlen; + if (!EVP_DecryptUpdate(&cipherContext, outbuf, &outlen, input, inlen)) { + SETNSERROR(@"NSDataEncryptErrorDomain", -1, @"EVP_DecryptUpdate: %@", [OpenSSL errorMessage]); + return nil; + } + int templen; + if (!EVP_DecryptFinal(&cipherContext, outbuf + outlen, &templen)) { + SETNSERROR(@"NSDataEncryptErrorDomain", -1, @"EVP_DecryptFinal: %@", [OpenSSL errorMessage]); + return nil; + } + outlen += templen; + NSData *ret = [[[NSData alloc] initWithBytes:outbuf length:outlen] autorelease]; + free(outbuf); + return ret; +} +@end + +@implementation NSData (Encrypt) +- (NSData *)encryptWithCipher:(NSString *)cipherName key:(NSString *)key error:(NSError **)error { + NSData *ret = nil; + Crypter *crypter = [[Crypter alloc] initWithCipher:cipherName key:key data:self error:error]; + if (crypter != nil) { + ret = [crypter encrypt:error]; + [crypter release]; + } + return ret; +} + +- (NSData *)decryptWithCipher:(NSString *)cipherName key:(NSString *)key error:(NSError **)error { + NSData *ret = nil; + Crypter *crypter = [[Crypter alloc] initWithCipher:cipherName key:key data:self error:error]; + if (crypter != nil) { + ret = [crypter decrypt:error]; + [crypter release]; + } + return ret; +} + +@end diff --git a/crypto/OpenSSL.h b/crypto/OpenSSL.h new file mode 100644 index 0000000..df3a837 --- /dev/null +++ b/crypto/OpenSSL.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import + + +@interface OpenSSL : NSObject { + +} ++ (BOOL)initializeSSL:(NSError **)error; ++ (SSL_CTX *)context; ++ (NSString *)errorMessage; +@end diff --git a/crypto/OpenSSL.m b/crypto/OpenSSL.m new file mode 100644 index 0000000..5f23b0f --- /dev/null +++ b/crypto/OpenSSL.m @@ -0,0 +1,76 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import +#import "OpenSSL.h" +#import "SetNSError.h" + +static BOOL initialized = NO; +static SSL_CTX *ctx; + +@implementation OpenSSL ++ (BOOL)initializeSSL:(NSError **)error { + if (!initialized) { + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + ERR_load_crypto_strings(); + ctx = SSL_CTX_new(SSLv23_method()); + if (ctx == NULL) { + SETNSERROR(@"SSLErrorDomain", -1, @"SSL_CTX_new: %@", [OpenSSL errorMessage]); + return NO; + } + initialized = YES; + } + return YES; +} ++ (SSL_CTX *)context { + return ctx; +} ++ (NSString *)errorMessage { + NSMutableString *msg = [NSMutableString string]; + for (;;) { + unsigned long err = ERR_get_error(); + if (err == 0) { + break; + } + if ([msg length] > 0) { + [msg appendString:@"; "]; + } + [msg appendFormat:@"%s", ERR_error_string(err, NULL)]; + } + if ([msg length] == 0) { + [msg appendString:@"(no error)"]; + } + return msg; +} +@end diff --git a/crypto/SHA1Hash.h b/crypto/SHA1Hash.h new file mode 100644 index 0000000..b7a8825 --- /dev/null +++ b/crypto/SHA1Hash.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + + +#import +#import "InputStream.h" +@class Blob; + +@interface SHA1Hash : NSObject { + +} ++ (NSString *)hashData:(NSData *)data; ++ (NSString *)hashBlob:(Blob *)blob blobLength:(unsigned long long *)blobLength error:(NSError **)error; ++ (NSString *)hashStream:(id )is withlength:(uint64_t)length error:(NSError **)error; ++ (NSString *)hashFile:(NSString *)path error:(NSError **)error; +@end diff --git a/crypto/SHA1Hash.m b/crypto/SHA1Hash.m new file mode 100644 index 0000000..e73e52b --- /dev/null +++ b/crypto/SHA1Hash.m @@ -0,0 +1,134 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "SHA1Hash.h" +#include +#import "FileInputStream.h" +#import "NSErrorCodes.h" +#import "Blob.h" +#import "BufferedInputStream.h" + +@interface SHA1Hash (internal) ++ (NSString *)hashStream:(id )is error:(NSError **)error; ++ (NSString *)hashStream:(id )is streamLength:(unsigned long long *)streamLength error:(NSError **)error; +@end + +static NSString *digest2String(unsigned char *digest) { + char *str = (char *)calloc(SHA_DIGEST_LENGTH, 2); + for (int i = 0; i < SHA_DIGEST_LENGTH; i++) { + sprintf(&(str[i*2]), "%02x", digest[i]); + } + NSString *ret = [[[NSString alloc] initWithCString:str length:SHA_DIGEST_LENGTH*2] autorelease]; + free(str); + return ret; +} + +@implementation SHA1Hash ++ (NSString *)hashData:(NSData *)data { + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, [data bytes], (unsigned long)[data length]); + unsigned char md[SHA_DIGEST_LENGTH]; + SHA1_Final(md, &ctx); + return digest2String(md); +} ++ (NSString *)hashBlob:(Blob *)blob blobLength:(unsigned long long *)blobLength error:(NSError **)error { + id is = [blob newInputStream:self]; + if (is == nil) { + return nil; + } + NSString *sha1 = [SHA1Hash hashStream:is streamLength:blobLength error:error]; + [is release]; + return sha1; +} ++ (NSString *)hashFile:(NSString *)path error:(NSError **)error { + NSDictionary *attribs = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:error]; + if (attribs == nil) { + return NO; + } + FileInputStream *fis = [[FileInputStream alloc] initWithPath:path length:[[attribs objectForKey:NSFileSize] unsignedLongLongValue]]; + NSString *sha1 = [SHA1Hash hashStream:fis error:error]; + [fis release]; + return sha1; +} ++ (NSString *)hashStream:(id )bis withlength:(uint64_t)length error:(NSError **)error { + SHA_CTX ctx; + SHA1_Init(&ctx); + uint64_t received = 0; + while (received < length) { + uint64_t toRead = length - received; + NSUInteger thisLength = 0; + unsigned char *buf = [bis readMaximum:toRead length:&thisLength error:error]; + if (buf == NULL) { + return nil; + } + NSAssert(thisLength > 0, @"expected more than 0 bytes"); + SHA1_Update(&ctx, buf, (unsigned long)thisLength); + received += (uint64_t)thisLength; + } + unsigned char md[SHA_DIGEST_LENGTH]; + SHA1_Final(md, &ctx); + return digest2String(md); +} +@end + +@implementation SHA1Hash (internal) ++ (NSString *)hashStream:(id )is error:(NSError **)error { + unsigned long long length; + return [SHA1Hash hashStream:is streamLength:&length error:error]; +} ++ (NSString *)hashStream:(id )is streamLength:(unsigned long long *)streamLength error:(NSError **)error { + SHA_CTX ctx; + SHA1_Init(&ctx); + *streamLength = 0; + for (;;) { + NSUInteger length = 0; + NSError *myError; + unsigned char *buf = [is read:&length error:&myError]; + if (buf == NULL) { + if ([myError code] != ERROR_EOF) { + if (error != NULL) { + *error = myError; + } + return nil; + } + break; // EOF. + } + NSAssert(length > 0, @"expected more than 0 bytes"); + SHA1_Update(&ctx, buf, (unsigned long)length); + *streamLength += (unsigned long long)length; + } + unsigned char md[SHA_DIGEST_LENGTH]; + SHA1_Final(md, &ctx); + return digest2String(md); +} +@end diff --git a/http/HTTP.h b/http/HTTP.h new file mode 100644 index 0000000..aa83d6c --- /dev/null +++ b/http/HTTP.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + + +#define HTTP_1_1 @"1.1" +#define HTTP_INTERNAL_SERVER_ERROR (500) +#define HTTP_FORBIDDEN (403) +#define HTTP_BAD_REQUEST (400) +#define HTTP_CONFLICT (409) +#define HTTP_REQUESTED_RANGE_NOT_SATISFIABLE (416) +#define HTTP_LENGTH_REQUIRED (411) +#define HTTP_NOT_FOUND (404) +#define HTTP_MOVED_PERMANENTLY (301) +#define HTTP_MOVED_TEMPORARILY (307) +#define HTTP_SERVICE_NOT_AVAILABLE (503) diff --git a/http/HTTPConnection.h b/http/HTTPConnection.h new file mode 100644 index 0000000..54f3f14 --- /dev/null +++ b/http/HTTPConnection.h @@ -0,0 +1,59 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@protocol InputStream; +@protocol StreamPair; +@class HTTPRequest; +@class HTTPResponse; + +@interface HTTPConnection : NSObject { + id streamPair; + HTTPRequest *request; + HTTPResponse *response; +} +- (id)initWithHost:(NSString *)theHost useSSL:(BOOL)isUseSSL error:(NSError **)error; +- (void)setRequestMethod:(NSString *)theRequestMethod pathInfo:(NSString *)thePathInfo queryString:(NSString *)theQueryString protocol:(NSString *)theProtocol; +- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key; +- (void)setRequestHostHeader; +- (void)setRequestKeepAliveHeader; +- (void)setRequestContentDispositionHeader:(NSString *)downloadName; +- (void)setRFC822DateRequestHeader; +- (BOOL)executeRequest:(NSError **)error; +- (int)responseCode; +- (NSString *)responseHeaderForKey:(NSString *)key; +- (NSString *)responseMimeType; +- (NSString *)responseDownloadName; +- (id )newResponseBodyStream:(NSError **)error; +- (NSData *)slurpResponseBody:(NSError **)error; +- (void)setCloseRequested; +@end diff --git a/http/HTTPConnection.m b/http/HTTPConnection.m new file mode 100644 index 0000000..aa83303 --- /dev/null +++ b/http/HTTPConnection.m @@ -0,0 +1,135 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "HTTPConnection.h" +#import "HTTPRequest.h" +#import "HTTPResponse.h" +#import "StreamPairFactory.h" +#import "StreamPair.h" +#import "Streams.h" +#import "RegexKitLite.h" +#import "FDOutputStream.h" +#import "FDInputStream.h" + +@implementation HTTPConnection +- (id)initWithHost:(NSString *)theHost useSSL:(BOOL)isUseSSL error:(NSError **)error { + if (self = [super init]) { + streamPair = [[StreamPairFactory theFactory] newStreamPairToHost:theHost useSSL:isUseSSL error:error]; + if (streamPair == nil) { + [self release]; + return nil; + } + request = [[HTTPRequest alloc] initWithHost:theHost]; + response = [[HTTPResponse alloc] init]; + } + return self; +} +- (void)dealloc { + [streamPair release]; + [request release]; + [response release]; + [super dealloc]; +} +- (void)setRequestMethod:(NSString *)theRequestMethod pathInfo:(NSString *)thePathInfo queryString:(NSString *)theQueryString protocol:(NSString *)theProtocol { + [request setMethod:theRequestMethod]; + [request setPathInfo:thePathInfo]; + [request setQueryString:theQueryString]; + [request setProtocol:theProtocol]; +} +- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key { + [request setHeader:value forKey:key]; +} +- (void)setRequestHostHeader { + [request setHostHeader]; +} +- (void)setRequestKeepAliveHeader { + [request setKeepAliveHeader]; +} +- (void)setRequestContentDispositionHeader:(NSString *)downloadName { + [request setContentDispositionHeader:downloadName]; +} +- (void)setRFC822DateRequestHeader { + [request setRFC822DateHeader]; +} +- (BOOL)executeRequest:(NSError **)error { + if (![request write:streamPair error:error]) { + return NO; + } + if (![response readHead:streamPair requestMethod:[request method] error:error]) { + return NO; + } + if ([[response headerForKey:@"Connection"] isEqualToString:@"Close"]) { + HSLogDebug(@"Connection: Close header received; requesting close on %@", streamPair); + [streamPair setCloseRequested]; + } + if (![[response protocol] isEqualToString:@"1.1"]) { + HSLogDebug(@"protocol %@ HTTP response; requesting close on %@", [response protocol], streamPair); + [streamPair setCloseRequested]; + } + return YES; +} +- (int)responseCode { + return [response code]; +} +- (NSString *)responseHeaderForKey:(NSString *)key { + return [response headerForKey:key]; +} +- (NSString *)responseMimeType { + return [response headerForKey:@"Content-Type"]; +} +- (NSString *)responseDownloadName { + NSString *downloadName = nil; + NSString *contentDisposition = [response headerForKey:@"Content-Disposition"]; + if (contentDisposition != nil) { + NSRange filenameRange = [contentDisposition rangeOfRegex:@"attachment;filename=(.+)" capture:1]; + if (filenameRange.location != NSNotFound) { + downloadName = [contentDisposition substringWithRange:filenameRange]; + } + } + return downloadName; +} +- (id )newResponseBodyStream:(NSError **)error { + return [response newResponseInputStream:streamPair error:error]; +} +- (NSData *)slurpResponseBody:(NSError **)error { + id is = [self newResponseBodyStream:error]; + if (is == nil) { + return nil; + } + NSData *data = [is slurp:error]; + [is release]; + return data; +} +- (void)setCloseRequested { + [streamPair setCloseRequested]; +} +@end diff --git a/http/HTTPRequest.h b/http/HTTPRequest.h new file mode 100644 index 0000000..5a2df30 --- /dev/null +++ b/http/HTTPRequest.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@protocol OutputStream; +@class RFC2616DateFormatter; + +@interface HTTPRequest : NSObject { + NSString *host; + NSString *method; + NSString *pathInfo; + NSString *queryString; + NSString *protocol; + NSMutableDictionary *headers; + RFC2616DateFormatter *dateFormatter; +} +@property (copy, readonly) NSString *host; +@property (copy) NSString *method; +@property (copy) NSString *pathInfo; +@property (copy) NSString *queryString; +@property (copy) NSString *protocol; + +- (id)initWithHost:(NSString *)host; +- (void)setHeader:(NSString *)value forKey:(NSString *)key; +- (void)setHostHeader; +- (void)setKeepAliveHeader; +- (void)setRFC822DateHeader; +- (void)setContentDispositionHeader:(NSString *)downloadName; +- (NSString *)headerForKey:(NSString *)key; +- (NSArray *)allHeaderKeys; +- (BOOL)write:(id )os error:(NSError **)error; +@end diff --git a/http/HTTPRequest.m b/http/HTTPRequest.m new file mode 100644 index 0000000..37356d1 --- /dev/null +++ b/http/HTTPRequest.m @@ -0,0 +1,128 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "HTTPRequest.h" +#import "Writer.h" +#import "RFC2616DateFormatter.h" + +#define DEFAULT_HTTP_PORT (80) + +@implementation HTTPRequest +@synthesize host, method, pathInfo, queryString, protocol; + +- (id)initWithHost:(NSString *)theHost { + if (self = [super init]) { + host = [theHost retain]; + headers = [[NSMutableDictionary alloc] init]; + dateFormatter = [[RFC2616DateFormatter alloc] init]; + } + return self; +} + +- (void)dealloc { + [host release]; + [method release]; + [pathInfo release]; + [queryString release]; + [protocol release]; + [headers release]; + [dateFormatter release]; + [super dealloc]; +} +- (void)setHeader:(NSString *)value forKey:(NSString *)key { + [headers setValue:value forKey:key]; +} +- (void)setHostHeader { + [headers setValue:host forKey:@"Host"]; +} +- (void)setKeepAliveHeader { + [headers setValue:@"keep-alive" forKey:@"Connection"]; +} +- (void)setRFC822DateHeader { + [headers setValue:[dateFormatter rfc2616StringFromDate:[NSDate date]] forKey:@"Date"]; +} +- (void)setContentDispositionHeader:(NSString *)downloadName { + if (downloadName != nil) { + NSString *encodedFilename = [NSString stringWithFormat:@"\"%@\"", [downloadName stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\\\""]]; + NSString *contentDisposition = [NSString stringWithFormat:@"attachment;filename=%@", encodedFilename]; + [self setHeader:contentDisposition forKey:@"Content-Disposition"]; + } +} +- (NSString *)headerForKey:(NSString *)key { + return [headers objectForKey:key]; +} +- (NSArray *)allHeaderKeys { + return [headers allKeys]; +} +- (BOOL)write:(id )os error:(NSError **)error { + HSLogTrace(@"writing %@", self); + Writer *writer = [[Writer alloc] initWithOutputStream:os]; + BOOL ret = NO; + do { + if (![writer write:method error:error] + || ![writer write:@" " error:error] + || ![writer write:pathInfo error:error]) { + break; + } + if (queryString != nil) { + if (![writer write:queryString error:error]) { + break; + } + } + if (![writer write:@" HTTP/" error:error] + || ![writer write:protocol error:error] + || ![writer write:@"\r\n" error:error]) { + break; + } + for (NSString *key in [headers allKeys]) { + HSLogTrace(@"header: %@ = %@", key, [headers objectForKey:key]); + if (![writer write:key error:error] + || ![writer write:@": " error:error] + || ![writer write:[headers objectForKey:key] error:error] + || ![writer write:@"\r\n" error:error]) { + break; + } + } + if (![writer write:@"\r\n" error:error]) { + break; + } + ret = YES; + } while(0); + [writer release]; + return ret; +} + +#pragma mark NSObject protocol +- (NSString *)description { + return [NSString stringWithFormat:@"", method, pathInfo, (queryString != nil ? queryString : @""), protocol]; +} +@end diff --git a/http/HTTPResponse.h b/http/HTTPResponse.h new file mode 100644 index 0000000..c237aa6 --- /dev/null +++ b/http/HTTPResponse.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@protocol BufferedInputStream; +@protocol InputStream; +@class FDInputStream; + +@interface HTTPResponse : NSObject { + int code; + NSString *protocol; + NSMutableDictionary *headers; + NSString *requestMethod; +} +- (id)init; +- (BOOL)readHead:(id )is requestMethod:(NSString *)requestMethod error:(NSError **)error; +- (int)code; +- (NSString *)protocol; +- (NSString *)headerForKey:(NSString *)key; +- (unsigned long long)contentLength; +- (id )newResponseInputStream:(id )underlyingStream error:(NSError **)error; +@end diff --git a/http/HTTPResponse.m b/http/HTTPResponse.m new file mode 100644 index 0000000..8998906 --- /dev/null +++ b/http/HTTPResponse.m @@ -0,0 +1,155 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "HTTPResponse.h" +#import "RegexKitLite.h" +#import "SetNSError.h" +#import "BufferedInputStream.h" +#import "DataInputStream.h" +#import "ChunkedInputStream.h" +#import "FixedLengthInputStream.h" +#import "InputStream.h" +#import "InputStreams.h" +#import "NSData-InputStream.h" +#import "FDInputStream.h" + +#define MAX_HTTP_STATUS_LINE_LENGTH (8192) +#define MAX_HTTP_HEADER_LINE_LENGTH (8192) + +@implementation HTTPResponse +- (id)init { + if (self = [super init]) { + headers = [[NSMutableDictionary alloc] init]; + } + return self; +} +- (void)dealloc { + [requestMethod release]; + [protocol release]; + [headers release]; + [super dealloc]; +} +- (int)code { + return code; +} +- (NSString *)protocol { + return protocol; +} +- (NSString *)headerForKey:(NSString *)key { + return [headers objectForKey:key]; +} +- (unsigned long long)contentLength { + long long contentLength = 0; + NSString *str = [self headerForKey:@"Content-Length"]; + if (str != nil) { + NSScanner *scanner = [NSScanner scannerWithString:str]; + if (![scanner scanLongLong:&contentLength]) { + HSLogWarn(@"unable to scan Content-Length %@", str); + } + } + return (unsigned long long)contentLength; +} +- (id )newResponseInputStream:(id )underlyingStream error:(NSError **)error { + id ret = nil; + if ([requestMethod isEqualToString:@"HEAD"] || code == 204) { + ret = [[NSData data] newInputStream]; + } else { + NSString *transferEncoding = [self headerForKey:@"Transfer-Encoding"]; + NSString *contentLength = [self headerForKey:@"Content-Length"]; + if (transferEncoding != nil) { + if ([[transferEncoding lowercaseString] isEqualToString:@"chunked"]) { + ret = [[ChunkedInputStream alloc] initWithUnderlyingStream:underlyingStream]; + } else { + SETNSERROR(@"StreamErrorDomain", -1, @"unknown Transfer-Encoding: %@", transferEncoding); + } + } else if (contentLength != nil) { + int length = [contentLength intValue]; + ret = [[FixedLengthInputStream alloc] initWithUnderlyingStream:underlyingStream length:(NSUInteger)length]; + } else { + /* + * FIXME: handle multipart/byteranges media type. + * See rfc2616 section 4.4 ("message length"). + */ + HSLogWarn(@"response body with no content-length"); + ret = [underlyingStream retain]; + } + } + return ret; +} +- (BOOL)readHead:(id )inputStream requestMethod:(NSString *)theRequestMethod error:(NSError **)error { + [headers removeAllObjects]; + [requestMethod release]; + requestMethod = [theRequestMethod copy]; + NSString *line = [InputStreams readLineWithCRLF:inputStream maxLength:MAX_HTTP_STATUS_LINE_LENGTH error:error]; + if (line == nil) { + return NO; + } + NSString *pattern = @"^HTTP/(1.\\d)\\s+(\\d+)\\s+(.+)\r\n$"; + NSRange protoRange = [line rangeOfRegex:pattern capture:1]; + NSRange codeRange = [line rangeOfRegex:pattern capture:2]; + if (protoRange.location == NSNotFound || codeRange.location == NSNotFound) { + SETNSERROR(@"HTTPResponseErrorDomain", -1, @"unexpected response status line: %@", line); + return NO; + } + protocol = [[line substringWithRange:protoRange] retain]; + code = [[line substringWithRange:codeRange] intValue]; + + NSString *headerPattern = @"^([^:]+):\\s+(.+)\r\n$"; + for(;;) { + line = [InputStreams readLineWithCRLF:inputStream maxLength:MAX_HTTP_HEADER_LINE_LENGTH error:error]; + if (line == nil) { + return NO; + } + if ([line isEqualToString:@"\r\n"]) { + break; + } + NSRange nameRange = [line rangeOfRegex:headerPattern capture:1]; + NSRange valueRange = [line rangeOfRegex:headerPattern capture:2]; + if (nameRange.location == NSNotFound || valueRange.location == NSNotFound) { + SETNSERROR(@"HTTPResponseErrorDomain", -1, @"invalid response header: %@", line); + return NO; + } + NSString *name = [line substringWithRange:nameRange]; + NSString *value = [line substringWithRange:valueRange]; + if ([headers objectForKey:name] != nil) { + HSLogWarn(@"dumping response header %@ = %@", name, [headers objectForKey:name]); + } + [headers setObject:value forKey:name]; + } + return YES; +} + +#pragma mark NSObject protocol +- (NSString *)description { + return [NSString stringWithFormat:@"", self, code, headers]; +} +@end diff --git a/http/RFC2616DateFormatter.h b/http/RFC2616DateFormatter.h new file mode 100644 index 0000000..6c5f1aa --- /dev/null +++ b/http/RFC2616DateFormatter.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface RFC2616DateFormatter : NSObject { + NSLocale *usLocale; + NSDateFormatter *formatter; +} +- (NSString *)rfc2616StringFromDate:(NSDate *)date; +@end diff --git a/http/RFC2616DateFormatter.m b/http/RFC2616DateFormatter.m new file mode 100644 index 0000000..d83ba61 --- /dev/null +++ b/http/RFC2616DateFormatter.m @@ -0,0 +1,60 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "RFC2616DateFormatter.h" + + +@implementation RFC2616DateFormatter +- (id)init { + if (self = [super init]) { + formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss z"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + if (usLocale != nil) { + [formatter setLocale:usLocale]; + } else { + HSLogWarn(@"no en_US locale installed"); + } + } + return self; +} +- (void)dealloc { + [formatter release]; + [usLocale release]; + [super dealloc]; +} +- (NSString *)rfc2616StringFromDate:(NSDate *)date { + //FIXME: If US locale isn't available, put the English words into the date yourself, according to http://www.ietf.org/rfc/rfc2616.txt + return [formatter stringFromDate:date]; +} +@end diff --git a/io/BooleanIO.h b/io/BooleanIO.h new file mode 100644 index 0000000..08eb4ee --- /dev/null +++ b/io/BooleanIO.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" +#import "OutputStream.h" + +@interface BooleanIO : NSObject { + +} ++ (void)write:(BOOL)b to:(NSMutableData *)data; ++ (BOOL)write:(BOOL)b to:(id )os error:(NSError **)error; ++ (BOOL)read:(BOOL *)value from:(id )is error:(NSError **)error; +@end diff --git a/io/BooleanIO.m b/io/BooleanIO.m new file mode 100644 index 0000000..44b04e3 --- /dev/null +++ b/io/BooleanIO.m @@ -0,0 +1,55 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "BooleanIO.h" +#import "Streams.h" + +@implementation BooleanIO ++ (void)write:(BOOL)b to:(NSMutableData *)data { + char c = b ? 1 : 0; + [data appendBytes:&c length:1]; +} ++ (BOOL)write:(BOOL)b to:(id )os error:(NSError **)error { + unsigned char c = b ? 1 : 0; + return [os write:&c length:1 error:error]; + +} ++ (BOOL)read:(BOOL *)value from:(id )is error:(NSError **)error { + *value = NO; + unsigned char *bytes = [is readExactly:1 error:error]; + if (!bytes) { + return NO; + } + *value = bytes[0] != 0; + return YES; +} +@end diff --git a/io/BufferedInputStream.h b/io/BufferedInputStream.h new file mode 100644 index 0000000..8ca9f49 --- /dev/null +++ b/io/BufferedInputStream.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStream.h" + +@protocol BufferedInputStream +- (unsigned char *)readExactly:(NSUInteger)exactLength error:(NSError **)error; +- (unsigned char *)readMaximum:(NSUInteger)maximum length:(NSUInteger *)length error:(NSError **)error; +- (uint64_t)bytesReceived; +@end diff --git a/io/CFStreamInputStream.h b/io/CFStreamInputStream.h new file mode 100644 index 0000000..d833af3 --- /dev/null +++ b/io/CFStreamInputStream.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" + +@interface CFStreamInputStream : NSObject { + CFReadStreamRef readStream; + BOOL isOpen; + uint64_t bytesReceived; +} +- (id)initWithCFReadStream:(CFReadStreamRef)streamRef; +@end diff --git a/io/CFStreamInputStream.m b/io/CFStreamInputStream.m new file mode 100644 index 0000000..7212345 --- /dev/null +++ b/io/CFStreamInputStream.m @@ -0,0 +1,156 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "CFStreamInputStream.h" +#import "InputStreams.h" +#import "SetNSError.h" +#import "NSErrorCodes.h" +#import "CFStreamPair.h" + +#define MY_BUF_SIZE (4096) +#define DEFAULT_READ_TIMEOUT_SECONDS (60) + +@interface CFStreamInputStream (internal) +- (BOOL)open:(NSError **)error; +@end + +@implementation CFStreamInputStream +- (id)initWithCFReadStream:(CFReadStreamRef)streamRef { + if (self = [super init]) { + readStream = streamRef; + CFRetain(readStream); + } + return self; +} +- (void)dealloc { + CFRelease(readStream); + [super dealloc]; +} + +#pragma mark InputStream +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error { + if (![self open:error]) { + return NULL; + } + unsigned char *ret = [self readMaximum:MY_BUF_SIZE length:length error:error]; + if (ret != NULL) { + bytesReceived += (uint64_t)*length; + } + return ret; +} +- (NSData *)slurp:(NSError **)error { + if (![self open:error]) { + return nil; + } + return [InputStreams slurp:self error:error]; +} + +#pragma mark BufferedInputStream +- (unsigned char *)readExactly:(NSUInteger)exactLength error:(NSError **)error { + if (![self open:error]) { + return NULL; + } + if (exactLength > 2147483648) { + SETNSERROR(@"InputStreamErrorDomain", -1, @"absurd length %u requested", exactLength); + return NULL; + } + NSMutableData *data = [NSMutableData dataWithLength:exactLength]; + unsigned char *dataBuf = [data mutableBytes]; + NSUInteger total = 0; + while (total < exactLength) { + NSUInteger maximum = exactLength - total; + NSUInteger length; + unsigned char *ibuf = [self readMaximum:maximum length:&length error:error]; + if (ibuf == NULL) { + return NULL; + } + NSAssert(length > 0, @"expected more than 0 bytes"); + memcpy(dataBuf + total, ibuf, length); + total += length; + } + bytesReceived += (uint64_t)exactLength; + return dataBuf; +} +- (unsigned char *)readMaximum:(NSUInteger)maximum length:(NSUInteger *)length error:(NSError **)error { + if (![self open:error]) { + return NULL; + } + NSUInteger toRead = (MY_BUF_SIZE > maximum) ? maximum : MY_BUF_SIZE; + NSMutableData *data = [NSMutableData dataWithLength:toRead]; + unsigned char *buf = (unsigned char *)[data mutableBytes]; + CFIndex index = CFReadStreamRead(readStream, buf, toRead); + if (index == -1) { + if (error != NULL) { + CFErrorRef err = CFReadStreamCopyError(readStream); + if (err == NULL) { + SETNSERROR(@"CFStreamPairErrorDomain", -1, @"unknown network error"); + } else { + *error = [CFStreamPair NSErrorWithNetworkError:err]; + CFRelease(err); + } + } + return NULL; + } + if (index == 0) { + SETNSERROR(@"StreamErrorDomain", ERROR_EOF, @"EOF"); + return NULL; + } + *length = (NSUInteger)index; + bytesReceived += (uint64_t)index; + return buf; +} +- (void)bytesWereNotUsed { +} +- (uint64_t)bytesReceived { + return bytesReceived; +} +@end +@implementation CFStreamInputStream (internal) +- (BOOL)open:(NSError **)error { + if (!isOpen) { + if (!CFReadStreamOpen(readStream)) { + if (error != NULL) { + CFErrorRef err = CFReadStreamCopyError(readStream); + if (err == NULL) { + SETNSERROR(@"CFStreamPairErrorDomain", -1, @"unknown network error"); + } else { + *error = [CFStreamPair NSErrorWithNetworkError:err]; + CFRelease(err); + } + } + return NO; + } + isOpen = YES; + } + return YES; +} +@end diff --git a/io/CFStreamOutputStream.h b/io/CFStreamOutputStream.h new file mode 100644 index 0000000..60c0fc8 --- /dev/null +++ b/io/CFStreamOutputStream.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "OutputStream.h" + +@interface CFStreamOutputStream : NSObject { + CFWriteStreamRef writeStream; + BOOL isOpen; + unsigned long long bytesWritten; +} +- (id)initWithCFWriteStream:(CFWriteStreamRef)streamRef; +@end diff --git a/io/CFStreamOutputStream.m b/io/CFStreamOutputStream.m new file mode 100644 index 0000000..65d32cf --- /dev/null +++ b/io/CFStreamOutputStream.m @@ -0,0 +1,86 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "CFStreamOutputStream.h" +#import "SetNSError.h" +#import "CFStreamPair.h" + +@implementation CFStreamOutputStream +- (id)initWithCFWriteStream:(CFWriteStreamRef)streamRef { + if (self = [super init]) { + writeStream = streamRef; + CFRetain(streamRef); + } + return self; +} +- (void)dealloc { + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + [super dealloc]; +} + +#pragma mark OutputStream +- (BOOL)write:(const unsigned char *)buf length:(NSUInteger)len error:(NSError **)error { + if (!isOpen) { + Boolean ret = CFWriteStreamOpen(writeStream); + if (ret == false) { + SETNSERROR(@"CFStreamErrorDomain", -1, @"error opening write stream"); + return NO; + } + isOpen = YES; + } + CFIndex index = 0; + NSUInteger written = 0; + while ((len - written) > 0) { + write_again: + index = CFWriteStreamWrite(writeStream, &(buf[written]), len - written); + if (index == -1) { + if (error != NULL) { + CFErrorRef err = CFWriteStreamCopyError(writeStream); + if (err == NULL) { + SETNSERROR(@"CFStreamPairErrorDomain", -1, @"unknown network error"); + } else { + *error = [CFStreamPair NSErrorWithNetworkError:err]; + CFRelease(err); + } + } + return NO; + } + written += (unsigned long long)index; + bytesWritten += (unsigned long long)index; + } + return YES; +} +- (unsigned long long)bytesWritten { + return bytesWritten; +} +@end diff --git a/io/CFStreamPair.h b/io/CFStreamPair.h new file mode 100644 index 0000000..1877e1f --- /dev/null +++ b/io/CFStreamPair.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "StreamPair.h" +@class CFStreamInputStream; +@class CFStreamOutputStream; + +@interface CFStreamPair : NSObject { + NSString *description; + CFStreamInputStream *is; + CFStreamOutputStream *os; + NSTimeInterval createTime; + NSTimeInterval maxLifetime; + BOOL closeRequested; + +} ++ (NSString *)errorDomain; ++ (NSError *)NSErrorWithNetworkError:(CFErrorRef)err; +- (id)initWithHost:(NSString *)theHost useSSL:(BOOL)isUseSSL maxLifetime:(NSTimeInterval)theMaxLifetime; + +@end diff --git a/io/CFStreamPair.m b/io/CFStreamPair.m new file mode 100644 index 0000000..5475d76 --- /dev/null +++ b/io/CFStreamPair.m @@ -0,0 +1,235 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#include +#include +#import +#import "CFStreamPair.h" +#import "CFStreamInputStream.h" +#import "CFStreamOutputStream.h" +#import "DNS_SDErrors.h" + +static uint32_t HTTP_PORT = 80; +static uint32_t HTTPS_PORT = 443; + +@implementation CFStreamPair ++ (NSString *)errorDomain { + return @"CFStreamPairErrorDomain"; +} ++ (NSError *)NSErrorWithNetworkError:(CFErrorRef)err { + NSString *localizedDescription = @"Network error"; + NSString *domain = (NSString *)CFErrorGetDomain(err); + CFIndex code = CFErrorGetCode(err); + CFDictionaryRef userInfo = CFErrorCopyUserInfo(err); + if ([domain isEqualToString:(NSString *)kCFErrorDomainCFNetwork]) { + if (code == kCFHostErrorHostNotFound) { + localizedDescription = @"host not found"; + } else if (code == kCFHostErrorUnknown) { + int gaiCode = 0; + if (CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(userInfo, kCFGetAddrInfoFailureKey), kCFNumberIntType, &gaiCode)) { + HSLogDebug(@"Host lookup error: %s", gai_strerror(gaiCode)); + localizedDescription = @"Could not connect to the Internet"; + } + } else if (code == kCFSOCKSErrorUnknownClientVersion) { + localizedDescription = @"Unknown SOCKS client version"; + } else if (code == kCFSOCKSErrorUnsupportedServerVersion) { + localizedDescription = [NSString stringWithFormat:@"Unsupported SOCKS server version (server requested version %@)", (NSString *)CFDictionaryGetValue(userInfo, kCFSOCKSVersionKey)]; + } else if (code == kCFSOCKS4ErrorRequestFailed) { + localizedDescription = @"SOCKS4 request rejected or failed"; + } else if (code == kCFSOCKS4ErrorIdentdFailed) { + localizedDescription = @"SOCKS4 server cannot connect to identd on the client"; + } else if (code == kCFSOCKS4ErrorIdConflict) { + localizedDescription = @"SOCKS4 client and identd report different user IDs"; + } else if (code == kCFSOCKS4ErrorUnknownStatusCode) { + localizedDescription = [NSString stringWithFormat:@"SOCKS4 error %@", (NSString *)CFDictionaryGetValue(userInfo, kCFSOCKSStatusCodeKey)]; + } else if (code == kCFSOCKS5ErrorBadState) { + localizedDescription = @"SOCKS5 bad state"; + } else if (code == kCFSOCKS5ErrorBadResponseAddr) { + localizedDescription = @"SOCKS5 bad credentials"; + } else if (code == kCFSOCKS5ErrorBadCredentials) { + localizedDescription = @"SOCKS5 unsupported negotiation method"; + } else if (code == kCFSOCKS5ErrorUnsupportedNegotiationMethod) { + localizedDescription = @"SOCKS5 unsupported negotiation method"; + } else if (code == kCFSOCKS5ErrorNoAcceptableMethod) { + localizedDescription = @"SOCKS5 no acceptable method"; + } else if (code == kCFNetServiceErrorUnknown) { + localizedDescription = @"Unknown Net Services error"; + } else if (code == kCFNetServiceErrorCollision) { + localizedDescription = @"Net Services: collision"; + } else if (code == kCFNetServiceErrorNotFound) { + localizedDescription = @"Net Services: not found"; + } else if (code == kCFNetServiceErrorInProgress) { + localizedDescription = @"Net Services: in progress"; + } else if (code == kCFNetServiceErrorBadArgument) { + localizedDescription = @"Net Services: bad argument"; + } else if (code == kCFNetServiceErrorCancel) { + localizedDescription = @"Net Services: cancelled"; + } else if (code == kCFNetServiceErrorInvalid) { + localizedDescription = @"Net Services: invalid"; + } else if (code == kCFNetServiceErrorTimeout) { + localizedDescription = @"Net Services timeout"; + } else if (code == kCFNetServiceErrorDNSServiceFailure) { + localizedDescription = @"Net Services DNS failure"; + int dns_sdCode = 0; + if (CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(userInfo, kCFDNSServiceFailureKey), kCFNumberIntType, &dns_sdCode)) { + localizedDescription = [NSString stringWithFormat:@"Net Services DNS failure: %@", [DNS_SDErrors descriptionForDNS_SDError:dns_sdCode]]; + } + } else if (code == kCFFTPErrorUnexpectedStatusCode) { + localizedDescription = [NSString stringWithFormat:@"FTP error %@", (NSString *)CFDictionaryGetValue(userInfo, kCFFTPStatusCodeKey)]; + } else if (code == kCFErrorHTTPAuthenticationTypeUnsupported) { + localizedDescription = @"HTTP authentication type unsupported"; + } else if (code == kCFErrorHTTPBadCredentials) { + localizedDescription = @"bad HTTP credentials"; + } else if (code == kCFErrorHTTPConnectionLost) { + localizedDescription = @"HTTP connection lost"; + } else if (code == kCFErrorHTTPParseFailure) { + localizedDescription = @"HTTP parse failure"; + } else if (code == kCFErrorHTTPRedirectionLoopDetected) { + localizedDescription = @"HTTP redirection loop detected"; + } else if (code == kCFErrorHTTPBadURL) { + localizedDescription = @"bad HTTP URL"; + } else if (code == kCFErrorHTTPProxyConnectionFailure) { + localizedDescription = @"HTTP proxy connection failure"; + } else if (code == kCFErrorHTTPBadProxyCredentials) { + localizedDescription = @"bad HTTP proxy credentials"; + } else if (code == kCFErrorPACFileError) { + localizedDescription = @"HTTP PAC file error"; + } + } else if ([domain isEqualToString:@"NSPOSIXErrorDomain"] && code == ENOTCONN) { + localizedDescription = @"Lost connection to the Internet"; + } else { + localizedDescription = [(NSString *)CFErrorCopyDescription(err) autorelease]; + } + CFRelease(userInfo); + return [NSError errorWithDomain:[CFStreamPair errorDomain] code:code userInfo:[NSDictionary dictionaryWithObjectsAndKeys:localizedDescription, NSLocalizedDescriptionKey, nil]]; +} +- (id)initWithHost:(NSString *)theHost useSSL:(BOOL)isUseSSL maxLifetime:(NSTimeInterval)theMaxLifetime { + if (self = [super init]) { + description = [[NSString alloc] initWithFormat:@"", theHost, (isUseSSL ? @"YES" : @"NO")]; + CFReadStreamRef readStream; + CFWriteStreamRef writeStream; + CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)theHost, (isUseSSL ? HTTPS_PORT : HTTP_PORT), &readStream, &writeStream); + if (isUseSSL) { + NSDictionary *sslProperties = [NSDictionary dictionaryWithObjectsAndKeys: + (NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, + kCFBooleanTrue, kCFStreamSSLAllowsExpiredCertificates, + kCFBooleanTrue, kCFStreamSSLAllowsExpiredRoots, + kCFBooleanTrue, kCFStreamSSLAllowsAnyRoot, + kCFBooleanFalse, kCFStreamSSLValidatesCertificateChain, + kCFNull, kCFStreamSSLPeerName, + nil]; + + CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, sslProperties); + CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, sslProperties); + } + NSDictionary *proxyDict = (NSDictionary *)SCDynamicStoreCopyProxies(NULL); + if ([proxyDict objectForKey:(NSString *)kCFStreamPropertyHTTPProxyHost] != nil) { + CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict); + CFWriteStreamSetProperty(writeStream, kCFStreamPropertyHTTPProxy, proxyDict); + } + if ([proxyDict objectForKey:(NSString *)kCFStreamPropertySOCKSProxyHost] != nil && [proxyDict objectForKey:(NSString *)kCFStreamPropertySOCKSProxyPort] != nil) { + CFReadStreamSetProperty(readStream, kCFStreamPropertySOCKSProxy, proxyDict); + CFWriteStreamSetProperty(writeStream, kCFStreamPropertySOCKSProxy, proxyDict); + } + [proxyDict release]; + + is = [[CFStreamInputStream alloc] initWithCFReadStream:readStream]; + os = [[CFStreamOutputStream alloc] initWithCFWriteStream:writeStream]; + CFRelease(readStream); + CFRelease(writeStream); + createTime = [NSDate timeIntervalSinceReferenceDate]; + maxLifetime = theMaxLifetime; + } + return self; +} +- (void)dealloc { + [description release]; + [is release]; + [os release]; + [super dealloc]; +} +- (void)setCloseRequested { + closeRequested = YES; +} +- (BOOL)isUsable { + if (closeRequested) { + HSLogDebug(@"%@ close requested; not reusing", self); + return NO; + } + if (([NSDate timeIntervalSinceReferenceDate] - createTime) > maxLifetime) { + HSLogDebug(@"%@ > %f seconds old; not reusing", self, maxLifetime); + return NO; + } + return YES; +} + +#pragma mark BufferedInputStream +- (unsigned char *)readExactly:(NSUInteger)exactLength error:(NSError **)error { + return [is readExactly:exactLength error:error]; +} +- (unsigned char *)readMaximum:(NSUInteger)maximum length:(NSUInteger *)length error:(NSError **)error { + return [is readMaximum:maximum length:length error:error]; +} +- (uint64_t)bytesReceived { + return [is bytesReceived]; +} +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error { + return [is read:length error:error]; +} +- (NSData *)slurp:(NSError **)error { + return [is slurp:error]; +} +- (void)bytesWereNotUsed { +} + +#pragma mark OutputStream +- (BOOL)write:(const unsigned char *)buf length:(NSUInteger)len error:(NSError **)error { + NSError *myError = nil; + BOOL ret = [os write:buf length:len error:&myError]; + if (error != NULL) { + *error = myError; + } + if (!ret && [[myError domain] isEqualToString:@"UnixErrorDomain"] && [myError code] == EPIPE) { + HSLogError(@"broken pipe"); //FIXME: This may not work with CFStream stuff. + } + return ret; +} +- (unsigned long long)bytesWritten { + return [os bytesWritten]; +} + +#pragma mark NSObject +- (NSString *)description { + return description; +} +@end diff --git a/io/ChunkedInputStream.h b/io/ChunkedInputStream.h new file mode 100644 index 0000000..a2fc02a --- /dev/null +++ b/io/ChunkedInputStream.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStream.h" +@class FDInputStream; + +@interface ChunkedInputStream : NSObject { + FDInputStream *underlyingStream; + NSUInteger chunkLength; + NSUInteger received; +} +- (id)initWithUnderlyingStream:(FDInputStream *)is; +@end diff --git a/io/ChunkedInputStream.m b/io/ChunkedInputStream.m new file mode 100644 index 0000000..434146d --- /dev/null +++ b/io/ChunkedInputStream.m @@ -0,0 +1,92 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "ChunkedInputStream.h" +#import "SetNSError.h" +#import "InputStreams.h" +#import "NSErrorCodes.h" +#import "FDInputStream.h" + +#define MAX_CHUNK_LENGTH_LINE_LENGTH (1024) + +@implementation ChunkedInputStream +- (id)initWithUnderlyingStream:(FDInputStream *)is { + if (self = [super init]) { + underlyingStream = [is retain]; + } + return self; +} +- (void)dealloc { + [underlyingStream release]; + [super dealloc]; +} +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error { + if (received >= chunkLength) { + received = 0; + NSString *line = [InputStreams readLineWithCRLF:underlyingStream maxLength:MAX_CHUNK_LENGTH_LINE_LENGTH error:error]; + if (line == nil) { + return NULL; + } + NSScanner *scanner = [NSScanner scannerWithString:line]; + if (![scanner scanHexInt:&chunkLength]) { + SETNSERROR(@"StreamErrorDomain", -1, @"invalid chunk length: %@", line); + return NULL; + } + HSLogTrace(@"chunk length = %u", chunkLength); + } + unsigned char *buf = NULL; + if (chunkLength == 0) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF (zero chunk length)"); + } else { + buf = [underlyingStream readMaximum:(chunkLength - received) length:length error:error]; + if (buf) { + received += *length; + } + } + if (received >= chunkLength) { + NSString *line = [InputStreams readLineWithCRLF:underlyingStream maxLength:MAX_CHUNK_LENGTH_LINE_LENGTH error:error]; + if (line == nil) { + return NULL; + } + if (![line isEqualToString:@"\r\n"]) { + SETNSERROR(@"StreamErrorDomain", -1, @"missing CRLF at end of chunk!"); + return NULL; + } + } + return buf; +} +- (NSData *)slurp:(NSError **)error { + return [InputStreams slurp:self error:error]; +} +- (void)bytesWereNotUsed { +} +@end diff --git a/io/CryptInputStream.h b/io/CryptInputStream.h new file mode 100644 index 0000000..bd8ea02 --- /dev/null +++ b/io/CryptInputStream.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#include +#import "InputStream.h" + +typedef int (*CryptInitFunc)(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, unsigned char *key, unsigned char *iv); +typedef int (*CryptUpdateFunc)(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, unsigned char *in, int inl); +typedef int (*CryptFinalFunc)(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); + +@interface CryptInputStream : NSObject { + CryptInitFunc cryptInit; + CryptUpdateFunc cryptUpdate; + CryptFinalFunc cryptFinal; + id is; + NSUInteger totalInBytesRecvd; + unsigned char *outBuf; + NSUInteger outBufLen; + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX cipherContext; + unsigned char evp_key[EVP_MAX_KEY_LENGTH]; + unsigned char iv[EVP_MAX_IV_LENGTH]; + size_t blockSize; + BOOL initialized; + BOOL finalized; +} +- (id)initWithCryptInitFunc:(void *)theCryptInit cryptUpdateFunc:(void *)theCryptUpdate cryptFinalFunc:(void *)theCryptFinal inputStream:(id )theIS cipherName:(NSString *)theCipherName key:(NSString *)theKey error:(NSError **)error; +@end diff --git a/io/CryptInputStream.m b/io/CryptInputStream.m new file mode 100644 index 0000000..269ec93 --- /dev/null +++ b/io/CryptInputStream.m @@ -0,0 +1,187 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "CryptInputStream.h" +#import "SetNSError.h" +#import "OpenSSL.h" +#import "InputStreams.h" +#import "NSErrorCodes.h" + +@interface CryptInputStream (internal) +- (unsigned char *)readAtLeastBlockSize:(NSUInteger *)length error:(NSError **)error; +@end + +@implementation CryptInputStream +- (id)initWithCryptInitFunc:(void *)theCryptInit cryptUpdateFunc:(void *)theCryptUpdate cryptFinalFunc:(void *)theCryptFinal inputStream:(id )theIS cipherName:(NSString *)theCipherName key:(NSString *)theKey error:(NSError **)error { + if (self = [super init]) { + cryptInit = (CryptInitFunc)theCryptInit; + cryptUpdate = (CryptUpdateFunc)theCryptUpdate; + cryptFinal = (CryptFinalFunc)theCryptFinal; + BOOL ret = NO; + do { + is = [theIS retain]; + NSData *keyData = [theKey dataUsingEncoding:NSUTF8StringEncoding]; + if ([keyData length] > EVP_MAX_KEY_LENGTH) { + SETNSERROR(@"EncryptedInputStreamErrorDomain", -1, @"encryption key must be less than or equal to %d bytes", EVP_MAX_KEY_LENGTH); + break; + } + if (![OpenSSL initializeSSL:error]) { + break; + } + cipher = EVP_get_cipherbyname([theCipherName UTF8String]); + if (!cipher) { + SETNSERROR(@"EncryptedInputStreamErrorDomain", -1, @"failed to load %@ cipher: %@", theCipherName, [OpenSSL errorMessage]); + break; + } + evp_key[0] = 0; + EVP_BytesToKey(cipher, EVP_md5(), NULL, [keyData bytes], [keyData length], 1, evp_key, iv); + EVP_CIPHER_CTX_init(&cipherContext); + if (!(*cryptInit)(&cipherContext, cipher, evp_key, iv)) { + SETNSERROR(@"NSDataEncryptErrorDomain", -1, @"EVP_EncryptInit: %@", [OpenSSL errorMessage]); + break; + } + EVP_CIPHER_CTX_set_key_length(&cipherContext, EVP_MAX_KEY_LENGTH); + blockSize = (unsigned long long)EVP_CIPHER_CTX_block_size(&cipherContext); + initialized = YES; + ret = YES; + } while(0); + if (!ret) { + [self release]; + self = nil; + } + } + return self; +} +- (void)dealloc { + if (initialized) { + EVP_CIPHER_CTX_cleanup(&cipherContext); + } + if (outBuf != NULL) { + free(outBuf); + } + [is release]; + [super dealloc]; +} +- (BOOL)cryptUpdate:(int *)outLen inBuf:(unsigned char *)inBuf inLen:(NSUInteger)inLen { + @throw [NSException exceptionWithName:@"PureVirtualMethod" reason:@"don't call this" userInfo:nil]; +} +- (BOOL)cryptFinal:(int *)outLen { + @throw [NSException exceptionWithName:@"PureVirtualMethod" reason:@"don't call this" userInfo:nil]; +} +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error { + if (finalized) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"already finalized"); + return NULL; + } + NSUInteger inLen = 0; + int outLen = 0; + NSError *myError; + unsigned char *inBuf = [self readAtLeastBlockSize:&inLen error:&myError]; + if (inBuf == NULL && [myError code] != ERROR_EOF) { + if (error != NULL) { + *error = myError; + } + return NULL; + } + NSUInteger neededBufLen = inLen + blockSize; + if (outBufLen < neededBufLen) { + if (outBuf == NULL) { + outBuf = (unsigned char *)malloc(neededBufLen); + } else { + outBuf = (unsigned char *)realloc(outBuf, neededBufLen); + } + outBufLen = neededBufLen; + } + if (inBuf != NULL) { + NSAssert(inLen > 0, @"expected more than 0 input bytes"); + totalInBytesRecvd += inLen; + if (!(*cryptUpdate)(&cipherContext, outBuf, &outLen, inBuf, inLen)) { + SETNSERROR(@"OpenSSLErrorDomain", -1, @"crypt update: %@", [OpenSSL errorMessage]); + return NULL; + } + NSAssert(outLen < outBufLen, @"can't receive more bytes than outBufLen from EVP_EncryptUpdate"); + } + if (outLen == 0) { + finalized = YES; + if (totalInBytesRecvd > 0 && !(*cryptFinal)(&cipherContext, outBuf, &outLen)) { + SETNSERROR(@"OpenSSLErrorDomain", -1, @"crypt final: %@", [OpenSSL errorMessage]); + return NULL; + } + NSAssert(outLen < outBufLen, @"can't receive more bytes than outBufLen from EVP_EncryptFinal"); + } + if (outLen == 0) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF on encrypted input stream"); + return NULL; + } + NSAssert(outLen > 0, @"outLen must be greater than 0"); + *length = (NSUInteger)outLen; + return outBuf; +} +- (NSData *)slurp:(NSError **)error { + return [InputStreams slurp:self error:error]; +} +- (void)bytesWereNotUsed { +} + +@end +@implementation CryptInputStream (internal) +- (unsigned char *)readAtLeastBlockSize:(NSUInteger *)length error:(NSError **)error { + NSMutableData *data = [NSMutableData data]; + while ([data length] < blockSize) { + NSUInteger recvd = 0; + NSError *myError = nil; + unsigned char *buf = [is read:&recvd error:&myError]; + if (buf == NULL) { + if ([myError code] != ERROR_EOF) { + if (error != NULL) { + *error = myError; + } + return NULL; + } + break; + } + if (recvd > blockSize && [data length] == 0) { + // Short-circuit to avoid a buffer copy. + *length = recvd; + return buf; + } + [data appendBytes:buf length:recvd]; + } + if ([data length] == 0) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF"); + return NULL; + } + NSAssert([data length] > 0, @"must have received some bytes"); + *length = [data length]; + return [data mutableBytes]; +} +@end diff --git a/io/DataIO.h b/io/DataIO.h new file mode 100644 index 0000000..655d016 --- /dev/null +++ b/io/DataIO.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" + +@interface DataIO : NSObject { + +} ++ (void)write:(NSData *)data to:(NSMutableData *)data; ++ (BOOL)read:(NSData **)value from:(id )is error:(NSError **)error; + +@end diff --git a/io/DataIO.m b/io/DataIO.m new file mode 100644 index 0000000..28b05dd --- /dev/null +++ b/io/DataIO.m @@ -0,0 +1,56 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "DataIO.h" +#import "IntegerIO.h" +#import "Streams.h" + +@implementation DataIO ++ (void)write:(NSData *)data to:(NSMutableData *)outData { + [IntegerIO writeUInt64:(uint64_t)[data length] to:outData]; + [outData appendBytes:[data bytes] length:[data length]]; +} ++ (BOOL)read:(NSData **)value from:(id )is error:(NSError **)error { + *value = NO; + uint64_t length = 0; + if (![IntegerIO readUInt64:&length from:is error:error]) { + return NO; + } + unsigned char *bytes = [is readExactly:length error:error]; + if (!bytes) { + return NO; + } + *value = [NSData dataWithBytes:bytes length:length]; + return YES; +} + +@end diff --git a/io/DataInputStream.h b/io/DataInputStream.h new file mode 100644 index 0000000..9d3d9ef --- /dev/null +++ b/io/DataInputStream.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" + +@interface DataInputStream : NSObject { + NSData *data; + NSUInteger pos; +} +- (id)initWithData:(NSData *)theData; +- (id)initWithData:(NSData *)theData offset:(unsigned long long)theOffset length:(unsigned long long)theLength; +@end diff --git a/io/DataInputStream.m b/io/DataInputStream.m new file mode 100644 index 0000000..26601c4 --- /dev/null +++ b/io/DataInputStream.m @@ -0,0 +1,112 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "DataInputStream.h" +#import "SetNSError.h" +#import "NSErrorCodes.h" + +@implementation DataInputStream +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + data = [theData retain]; + } + return self; +} +- (id)initWithData:(NSData *)theData offset:(unsigned long long)theOffset length:(unsigned long long)theLength { + if (self = [super init]) { + data = [theData subdataWithRange:NSMakeRange((NSUInteger)theOffset, (NSUInteger)theLength)]; + } + return self; +} +- (void)dealloc { + [data release]; + [super dealloc]; +} + +#pragma mark InputStream protocol +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error { + if (pos >= [data length]) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF on data"); + return NULL; + } + NSUInteger remaining = [data length] - pos; + *length = remaining; + unsigned char *ret = (unsigned char *)[data bytes] + pos; + pos = [data length]; + return ret; +} +- (unsigned char *)readMaximum:(NSUInteger)maximum length:(NSUInteger *)length error:(NSError **)error { + if (pos >= [data length]) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF on data"); + return NULL; + } + NSUInteger len = [data length] - pos; + if (len > maximum) { + len = maximum; + } + unsigned char *buf = (unsigned char *)[data bytes] + pos; + pos += len; + return buf; +} +- (NSData *)slurp:(NSError **)error { + if (pos == 0) { + /* This is both a short-circuit and a hack. + * The short-circuit part is avoiding copying 'data'. + * The hack part is if [data length] == 0, we return the empty 'data' instead of an EOF error. + */ + return [[data retain] autorelease]; + } + if (pos >= [data length]) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF on data"); + return NULL; + } + NSData *ret = [data subdataWithRange:NSMakeRange(pos, [data length] - pos)]; + pos = [data length]; + return ret; +} +- (void)bytesWereNotUsed { +} +- (uint64_t)bytesReceived { + return (uint64_t)pos; +} + +#pragma mark BufferedInputStream protocol +- (unsigned char *)readExactly:(NSUInteger)exactLength error:(NSError **)error { + if (([data length] - pos) < exactLength) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF"); + return NULL; + } + unsigned char *buf = (unsigned char *)[data bytes] + pos; + pos += exactLength; + return buf; +} +@end diff --git a/io/DataInputStreamFactory.h b/io/DataInputStreamFactory.h new file mode 100644 index 0000000..8c03b73 --- /dev/null +++ b/io/DataInputStreamFactory.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStreamFactory.h" + +@interface DataInputStreamFactory : NSObject { + NSData *data; +} +- (id)initWithData:(NSData *)theData; +@end diff --git a/io/DataInputStreamFactory.m b/io/DataInputStreamFactory.m new file mode 100644 index 0000000..83c9a18 --- /dev/null +++ b/io/DataInputStreamFactory.m @@ -0,0 +1,56 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "DataInputStreamFactory.h" +#import "DataInputStream.h" +#import "NSData-InputStream.h" + +@implementation DataInputStreamFactory +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + data = [theData retain]; + } + return self; +} +- (void)dealloc { + [data release]; + [super dealloc]; +} + +#pragma mark InputStreamFactory protocol +- (id ) newInputStream:(id)sender { + return [data newInputStream]; +} +- (id ) newInputStream:(id)sender sourceOffset:(unsigned long long)theOffset sourceLength:(unsigned long long)theLength { + return [[DataInputStream alloc] initWithData:data offset:theOffset length:theLength]; +} +@end diff --git a/io/DateIO.h b/io/DateIO.h new file mode 100644 index 0000000..61b843e --- /dev/null +++ b/io/DateIO.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" + +@interface DateIO : NSObject { + +} ++ (void)write:(NSDate *)date to:(NSMutableData *)data; ++ (BOOL)read:(NSDate **)date from:(id )is error:(NSError **)error; +@end diff --git a/io/DateIO.m b/io/DateIO.m new file mode 100644 index 0000000..4fbc4e8 --- /dev/null +++ b/io/DateIO.m @@ -0,0 +1,61 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "BooleanIO.h" +#import "IntegerIO.h" +#import "DateIO.h" + +@implementation DateIO ++ (void)write:(NSDate *)date to:(NSMutableData *)data { + BOOL dateNotNil = (date != nil); + [BooleanIO write:dateNotNil to:data]; + if (dateNotNil) { + long long millisecondsSince1970 = (long long)([date timeIntervalSince1970] * 1000.0); + [IntegerIO writeInt64:millisecondsSince1970 to:data]; + } +} ++ (BOOL)read:(NSDate **)date from:(id )is error:(NSError **)error { + *date = nil; + BOOL notNil; + if (![BooleanIO read:¬Nil from:is error:error]) { + return NO; + } + if (notNil) { + long long millisecondsSince1970; + if (![IntegerIO readInt64:&millisecondsSince1970 from:is error:error]) { + return NO; + } + *date = [NSDate dateWithTimeIntervalSince1970:((double)millisecondsSince1970 / 1000.0)]; + } + return YES; +} +@end diff --git a/io/DecryptedInputStream.h b/io/DecryptedInputStream.h new file mode 100644 index 0000000..6a5af39 --- /dev/null +++ b/io/DecryptedInputStream.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "CryptInputStream.h" + +@interface DecryptedInputStream : CryptInputStream { +} +- (id)initWithInputStream:(id )theIS cipherName:(NSString *)theCipherName key:(NSString *)theKey error:(NSError **)error; +@end diff --git a/io/DecryptedInputStream.m b/io/DecryptedInputStream.m new file mode 100644 index 0000000..300ce22 --- /dev/null +++ b/io/DecryptedInputStream.m @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "DecryptedInputStream.h" +#include + +@implementation DecryptedInputStream +- (id)initWithInputStream:(id )theIS cipherName:(NSString *)theCipherName key:(NSString *)theKey error:(NSError **)error { + self = [super initWithCryptInitFunc:&EVP_DecryptInit cryptUpdateFunc:&EVP_DecryptUpdate cryptFinalFunc:&EVP_DecryptFinal inputStream:theIS cipherName:theCipherName key:theKey error:error]; + return self; +} +@end diff --git a/io/DoubleIO.h b/io/DoubleIO.h new file mode 100644 index 0000000..34d6004 --- /dev/null +++ b/io/DoubleIO.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" + +@interface DoubleIO : NSObject { + +} ++ (void)write:(double)d to:(NSMutableData *)data; ++ (BOOL)read:(double *)value from:(id )is error:(NSError **)error; +@end diff --git a/io/DoubleIO.m b/io/DoubleIO.m new file mode 100644 index 0000000..b372b7e --- /dev/null +++ b/io/DoubleIO.m @@ -0,0 +1,65 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "DoubleIO.h" +#import "StringIO.h" +#import "SetNSError.h" + + +//FIXME: Delete this class? It's not used anywhere. + + +@implementation DoubleIO ++ (void)write:(double)d to:(NSMutableData *)data { + NSString *str = [NSString stringWithFormat:@"%f", d]; + [StringIO write:str to:data]; +} ++ (BOOL)read:(double *)value from:(id )is error:(NSError **)error { + if (error) { + *error = 0; + } + *value = 0; + NSString *str; + if (![StringIO read:&str from:is error:error]) { + return NO; + } + if (!str) { + SETNSERROR(@"DoubleIOErrorDomain", -1, @"nil string; expected a double"); + return NO; + } + BOOL ret = [[NSScanner scannerWithString:str] scanDouble:value]; + if (!ret) { + SETNSERROR(@"DoubleIOErrorDomain", -1, @"%@ does not contain a double", str); + } + return ret; +} +@end diff --git a/io/EncryptedInputStream.h b/io/EncryptedInputStream.h new file mode 100644 index 0000000..4503e26 --- /dev/null +++ b/io/EncryptedInputStream.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "CryptInputStream.h" + +@interface EncryptedInputStream : CryptInputStream { +} +- (id)initWithInputStream:(id )theIS cipherName:(NSString *)theCipherName key:(NSString *)theKey error:(NSError **)error; +@end \ No newline at end of file diff --git a/io/EncryptedInputStream.m b/io/EncryptedInputStream.m new file mode 100644 index 0000000..d70b2b0 --- /dev/null +++ b/io/EncryptedInputStream.m @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "EncryptedInputStream.h" +#import + +@implementation EncryptedInputStream +- (id)initWithInputStream:(id )theIS cipherName:(NSString *)theCipherName key:(NSString *)theKey error:(NSError **)error { + self = [super initWithCryptInitFunc:&EVP_EncryptInit cryptUpdateFunc:&EVP_EncryptUpdate cryptFinalFunc:&EVP_EncryptFinal inputStream:theIS cipherName:theCipherName key:theKey error:error]; + return self; +} +@end diff --git a/io/FDInputStream.h b/io/FDInputStream.h new file mode 100644 index 0000000..d08f0ee --- /dev/null +++ b/io/FDInputStream.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" + +@interface FDInputStream : NSObject { + int fd; + uint64_t bytesReceived; +} ++ (void)setReadTimeoutSeconds:(time_t)timeout; +- (id)initWithFD:(int)theFD; +@end diff --git a/io/FDInputStream.m b/io/FDInputStream.m new file mode 100644 index 0000000..77defd6 --- /dev/null +++ b/io/FDInputStream.m @@ -0,0 +1,141 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "FDInputStream.h" +#import "SetNSError.h" +#import "InputStreams.h" +#import "NSErrorCodes.h" + +#define MY_BUF_SIZE (4096) +#define DEFAULT_READ_TIMEOUT_SECONDS (60) + +static time_t readTimeoutSeconds = DEFAULT_READ_TIMEOUT_SECONDS; + +@implementation FDInputStream ++ (void)setReadTimeoutSeconds:(time_t)timeout { + readTimeoutSeconds = timeout; + HSLogInfo(@"network read timeout set to %d seconds", timeout); +} +- (id)initWithFD:(int)theFD { + if (self = [super init]) { + fd = theFD; + } + return self; +} +- (void)dealloc { + [super dealloc]; +} +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error { + return [self readMaximum:MY_BUF_SIZE length:length error:error]; +} +- (unsigned char *)readMaximum:(NSUInteger)maximum length:(NSUInteger *)length error:(NSError **)error { + NSUInteger toRead = (MY_BUF_SIZE > maximum) ? maximum : MY_BUF_SIZE; + NSMutableData *data = [NSMutableData dataWithLength:toRead]; + unsigned char *buf = (unsigned char *)[data mutableBytes]; + int ret = 0; + fd_set readSet; + fd_set exceptSet; + FD_ZERO(&readSet); + FD_SET((unsigned int)fd, &readSet); + FD_ZERO(&exceptSet); + FD_SET((unsigned int)fd, &exceptSet); + struct timeval timeout; + struct timeval *pTimeout; + +select_again: + timeout.tv_sec = readTimeoutSeconds; + timeout.tv_usec = 0; + pTimeout = (readTimeoutSeconds > 0) ? &timeout : 0; + ret = select(fd + 1, &readSet, 0, &exceptSet, pTimeout); + if ((ret == -1) && (errno == EINTR)) { + goto select_again; + } else if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"select: %s", strerror(errno)); + return NULL; + } else if (ret == 0) { + SETNSERROR(@"InputStreamErrorDomain", -1, @"read timeout"); + return NULL; + } + +read_again: + ret = read(fd, buf, toRead); + if ((ret == -1) && (errno == EINTR)) { + goto read_again; + } else if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"read: %s", strerror(errno)); + return NULL; + } + if (ret == 0) { + SETNSERROR(@"StreamErrorDomain", ERROR_EOF, @"EOF on fd %d", fd); + return NULL; + } + *length = (NSUInteger)ret; + bytesReceived += (uint64_t)ret; + return buf; +} +- (NSData *)slurp:(NSError **)error { + return [InputStreams slurp:self error:error]; +} +- (void)bytesWereNotUsed { +} + +#pragma mark BufferedInputStream protocol +- (unsigned char *)readExactly:(NSUInteger)exactLength error:(NSError **)error { + if (exactLength > 2147483648) { + SETNSERROR(@"InputStreamErrorDomain", -1, @"absurd length %u requested", exactLength); + return NULL; + } + NSMutableData *data = [NSMutableData dataWithLength:exactLength]; + unsigned char *dataBuf = [data mutableBytes]; + NSUInteger total = 0; + while (total < exactLength) { + NSUInteger maximum = exactLength - total; + NSUInteger length; + unsigned char *ibuf = [self readMaximum:maximum length:&length error:error]; + if (ibuf == NULL) { + return NULL; + } + NSAssert(length > 0, @"expected more than 0 bytes"); + memcpy(dataBuf + total, ibuf, length); + total += length; + } + bytesReceived += (uint64_t)exactLength; + return dataBuf; +} +- (uint64_t)bytesReceived { + return bytesReceived; +} +#pragma mark NSObject protocol +- (NSString *)description { + return [NSString stringWithFormat:@"", fd]; +} +@end diff --git a/io/FDOutputStream.h b/io/FDOutputStream.h new file mode 100644 index 0000000..4b1927e --- /dev/null +++ b/io/FDOutputStream.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "OutputStream.h" + +@interface FDOutputStream : NSObject { + int fd; + unsigned long long bytesWritten; +} +- (id)initWithFD:(int)theFD; + +@end diff --git a/io/FDOutputStream.m b/io/FDOutputStream.m new file mode 100644 index 0000000..361b5c0 --- /dev/null +++ b/io/FDOutputStream.m @@ -0,0 +1,64 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "FDOutputStream.h" +#import "SetNSError.h" + +@implementation FDOutputStream +- (id)initWithFD:(int)theFD { + if (self = [super init]) { + fd = theFD; + } + return self; +} +- (BOOL)write:(const unsigned char *)buf length:(NSUInteger)len error:(NSError **)error { + int ret = 0; + NSUInteger written = 0; + while ((len - written) > 0) { + write_again: + ret = write(fd, &(buf[written]), len - written); + if ((ret == -1) && (errno == EINTR)) { + goto write_again; + } + if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"write: %s", strerror(errno)); + return NO; + } + written += (NSUInteger)ret; + bytesWritten += (NSUInteger)ret; + } + return YES; +} +- (unsigned long long)bytesWritten { + return bytesWritten; +} +@end diff --git a/io/FileInputStream.h b/io/FileInputStream.h new file mode 100644 index 0000000..4756d14 --- /dev/null +++ b/io/FileInputStream.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" + +@interface FileInputStream : NSObject { + int fd; + NSString *path; + unsigned long long fileLength; + unsigned long long offset; + unsigned char *buf; + uint64_t bytesReceived; +} +- (id)initWithPath:(NSString *)thePath length:(unsigned long long)theLength; +- (id)initWithPath:(NSString *)thePath offset:(unsigned long long)theOffset length:(unsigned long long)theLength; +@end diff --git a/io/FileInputStream.m b/io/FileInputStream.m new file mode 100644 index 0000000..e608577 --- /dev/null +++ b/io/FileInputStream.m @@ -0,0 +1,162 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "FileInputStream.h" +#import "SetNSError.h" +#import "InputStreams.h" +#import "NSErrorCodes.h" + +#define MY_BUF_SIZE (4096) + +@interface FileInputStream (internal) +- (void)close; +@end + +@implementation FileInputStream +- (id)initWithPath:(NSString *)thePath length:(unsigned long long)theLength { + if (self = [super init]) { + fd = -1; + path = [thePath retain]; + fileLength = theLength; + buf = (unsigned char *)malloc(MY_BUF_SIZE); + } + return self; +} +- (id)initWithPath:(NSString *)thePath offset:(unsigned long long)theOffset length:(unsigned long long)theLength { + if (self = [super init]) { + fd = -1; + path = [thePath retain]; + fileLength = theOffset + theLength; + buf = (unsigned char *)malloc(MY_BUF_SIZE); + offset = theOffset; + } + return self; +} +- (void)dealloc { + [self close]; + [path release]; + free(buf); + [super dealloc]; +} +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error { + return [self readMaximum:MY_BUF_SIZE length:length error:error]; +} +- (unsigned char *)readMaximum:(NSUInteger)maximum length:(NSUInteger *)length error:(NSError **)error { + if (fd == -1) { + fd = open([path fileSystemRepresentation], O_RDONLY|O_NOFOLLOW); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s", strerror(errno)); + return NO; + } + HSLogTrace(@"opened fd %d (%@)", fd, path); + if (offset > 0) { + if (lseek(fd, (off_t)offset, SEEK_SET) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"lseek(%@, %qu): %s", path, offset, strerror(errno)); + return NO; + } + } + } + int ret; + unsigned long long remaining = fileLength - offset; + unsigned long long toRead = (maximum > remaining) ? remaining : maximum; + if (toRead > MY_BUF_SIZE) { + toRead = MY_BUF_SIZE; + } + if (toRead == 0) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"reached EOF"); + return NULL; + } +read_again: + ret = read(fd, buf, (size_t)toRead); + if ((ret == -1) && (errno == EINTR)) { + goto read_again; + } + if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"read: %s", strerror(errno)); + return NULL; + } + if (ret == 0) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF on %@", path); + return NULL; + } + offset += (unsigned long long)ret; + *length = (NSUInteger)ret; + bytesReceived += (uint64_t)ret; + return buf; +} +- (NSData *)slurp:(NSError **)error { + return [InputStreams slurp:self error:error]; +} + +#pragma mark BufferedInputStream +- (unsigned char *)readExactly:(NSUInteger)exactLength error:(NSError **)error { + if (exactLength > 2147483648) { + SETNSERROR(@"InputStreamErrorDomain", -1, @"absurd length %u requested", exactLength); + return NULL; + } + NSMutableData *data = [NSMutableData dataWithLength:exactLength]; + unsigned char *dataBuf = [data mutableBytes]; + NSUInteger total = 0; + while (total < exactLength) { + NSUInteger maximum = exactLength - total; + NSUInteger length; + unsigned char *ibuf = [self readMaximum:maximum length:&length error:error]; + if (ibuf == NULL) { + return NULL; + } + NSAssert(length > 0, @"expected more than 0 bytes"); + memcpy(dataBuf + total, ibuf, length); + total += length; + } + return dataBuf; +} +- (uint64_t)bytesReceived { + return bytesReceived; +} +- (void)bytesWereNotUsed { +} + +#pragma mark NSObject protocol +- (NSString *)description { + return [NSString stringWithFormat:@"", fd, path]; +} +@end + +@implementation FileInputStream (internal) +- (void)close { + if (fd != -1) { + close(fd); + HSLogTrace(@"closed fd %d (%@)", fd, path); + fd = -1; + } +} +@end diff --git a/io/FileInputStreamFactory.h b/io/FileInputStreamFactory.h new file mode 100644 index 0000000..fa811c1 --- /dev/null +++ b/io/FileInputStreamFactory.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStreamFactory.h" + +@interface FileInputStreamFactory : NSObject { + NSString *path; + unsigned long long length; +} +- (id)initWithPath:(NSString *)thePath error:(NSError **)error; +@end diff --git a/io/FileInputStreamFactory.m b/io/FileInputStreamFactory.m new file mode 100644 index 0000000..3991883 --- /dev/null +++ b/io/FileInputStreamFactory.m @@ -0,0 +1,58 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "FileInputStreamFactory.h" +#import "FileInputStream.h" + +@implementation FileInputStreamFactory +- (id)initWithPath:(NSString *)thePath error:(NSError **)error { + if (self = [super init]) { + path = [thePath copy]; + NSDictionary *attribs = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:error]; + if (attribs == nil) { + return nil; + } + length = [[attribs objectForKey:NSFileSize] unsignedLongLongValue]; + } + return self; +} +- (void)dealloc { + [path release]; + [super dealloc]; +} +- (id ) newInputStream:(id)sender { + return [[FileInputStream alloc] initWithPath:path length:length]; +} +- (id ) newInputStream:(id)sender sourceOffset:(unsigned long long)theOffset sourceLength:(unsigned long long)theLength { + return [[FileInputStream alloc] initWithPath:path offset:theOffset length:length]; +} +@end diff --git a/io/FileOutputStream.h b/io/FileOutputStream.h new file mode 100644 index 0000000..1a41eab --- /dev/null +++ b/io/FileOutputStream.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "OutputStream.h" + +@interface FileOutputStream : NSObject { + BOOL append; + int fd; + NSString *path; + unsigned long long bytesWritten; +} +- (id)initWithPath:(NSString *)thePath append:(BOOL)isAppend; +- (void)close; +@end diff --git a/io/FileOutputStream.m b/io/FileOutputStream.m new file mode 100644 index 0000000..500acf5 --- /dev/null +++ b/io/FileOutputStream.m @@ -0,0 +1,92 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "FileOutputStream.h" +#import "SetNSError.h" + +@implementation FileOutputStream +- (id)initWithPath:(NSString *)thePath append:(BOOL)isAppend { + if (self = [super init]) { + path = [thePath copy]; + append = isAppend; + fd = -1; + } + return self; +} +- (void)dealloc { + if (fd != -1) { + close(fd); + } + [path release]; + [super dealloc]; +} +- (void)close { + if (fd != -1) { + close(fd); + fd = -1; + } +} +- (BOOL)write:(const unsigned char *)buf length:(NSUInteger)len error:(NSError **)error { + if (fd == -1) { + int oflag = O_WRONLY|O_CREAT; + if (append) { + oflag |= O_APPEND; + } else { + oflag |= O_TRUNC; + } + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + fd = open([path fileSystemRepresentation], oflag, mode); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s", strerror(errno)); + return NO; + } + } + int ret = 0; + NSUInteger written = 0; + while ((len - written) > 0) { + write_again: + ret = write(fd, &(buf[written]), len - written); + if ((ret == -1) && (errno == EINTR)) { + goto write_again; + } else if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"write: %s", strerror(errno)); + return NO; + } + written += (NSUInteger)ret; + bytesWritten += (NSUInteger)ret; + } + return YES; +} +- (unsigned long long)bytesWritten { + return bytesWritten; +} +@end diff --git a/io/FixedLengthInputStream.h b/io/FixedLengthInputStream.h new file mode 100644 index 0000000..7aafc13 --- /dev/null +++ b/io/FixedLengthInputStream.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStream.h" +@class FDInputStream; + +@interface FixedLengthInputStream : NSObject { + FDInputStream *underlyingStream; + unsigned long long fixedLength; + unsigned long long totalReceived; +} +- (id)initWithUnderlyingStream:(FDInputStream *)is length:(unsigned long long)theLength; +@end diff --git a/io/FixedLengthInputStream.m b/io/FixedLengthInputStream.m new file mode 100644 index 0000000..4280c51 --- /dev/null +++ b/io/FixedLengthInputStream.m @@ -0,0 +1,69 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "FixedLengthInputStream.h" +#import "InputStreams.h" +#import "SetNSError.h" +#import "NSErrorCodes.h" +#import "FDInputStream.h" + +@implementation FixedLengthInputStream +- (id)initWithUnderlyingStream:(FDInputStream *)is length:(unsigned long long)theLength { + if (self = [super init]) { + underlyingStream = [is retain]; + fixedLength = theLength; + } + return self; +} +- (void)dealloc { + [underlyingStream release]; + [super dealloc]; +} +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error { + unsigned long long maximum = fixedLength - totalReceived; + if (maximum == 0) { + SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF on fixed length input stream"); + return NULL; + } + unsigned char *buf = [underlyingStream readMaximum:maximum length:length error:error]; + if (buf == NULL) { + return NULL; + } + totalReceived += (unsigned long long)(*length); + return buf; +} +- (NSData *)slurp:(NSError **)error { + return [InputStreams slurp:self error:error]; +} +- (void)bytesWereNotUsed { +} +@end diff --git a/io/InputStream.h b/io/InputStream.h new file mode 100644 index 0000000..ec31666 --- /dev/null +++ b/io/InputStream.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@protocol InputStream +- (unsigned char *)read:(NSUInteger *)length error:(NSError **)error; +- (NSData *)slurp:(NSError **)error; +- (void)bytesWereNotUsed; +@end diff --git a/io/InputStreamFactory.h b/io/InputStreamFactory.h new file mode 100644 index 0000000..eba7b54 --- /dev/null +++ b/io/InputStreamFactory.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStream.h" + +@protocol InputStreamFactory + +// Returns an object that must be released by the caller. +- (id ) newInputStream:(id)sender; + +// Returns an object that must be released by the caller. +- (id ) newInputStream:(id)sender sourceOffset:(unsigned long long)theOffset sourceLength:(unsigned long long)theLength; +@end diff --git a/io/InputStreams.h b/io/InputStreams.h new file mode 100644 index 0000000..90da808 --- /dev/null +++ b/io/InputStreams.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStream.h" +@class FDInputStream; + +@interface InputStreams : NSObject { + +} ++ (NSData *)slurp:(id )is error:(NSError **)error; ++ (NSString *)readLineWithCRLF:(FDInputStream *)is maxLength:(NSUInteger)maxLength error:(NSError **)error; ++ (NSString *)readLine:(FDInputStream *)is error:(NSError **)error; +@end diff --git a/io/InputStreams.m b/io/InputStreams.m new file mode 100644 index 0000000..b3f31c0 --- /dev/null +++ b/io/InputStreams.m @@ -0,0 +1,115 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "InputStreams.h" +#import "SetNSError.h" +#import "NSErrorCodes.h" +#import "FDInputStream.h" + + +@implementation InputStreams ++ (NSData *)slurp:(id )is error:(NSError **)error { + NSMutableData *data = [[[NSMutableData alloc] init] autorelease]; + for (;;) { + NSError *myError = nil; + NSUInteger received; + unsigned char *buf = [is read:&received error:&myError]; + if (buf == NULL) { + if ([myError code] != ERROR_EOF) { + data = nil; + if (error != NULL) { + *error = myError; + } + } + break; + } + [data appendBytes:buf length:received]; + } + return data; +} ++ (NSString *)readLineWithCRLF:(FDInputStream *)is maxLength:(NSUInteger)maxLength error:(NSError **)error { + NSMutableData *data = [[[NSMutableData alloc] init] autorelease]; + for (;;) { + if ([data length] > maxLength) { + SETNSERROR(@"InputStreamErrorDomain", -1, @"exceeded maxLength %u", maxLength); + return nil; + } + NSUInteger received = 0; + unsigned char *buf = [is readMaximum:1 length:&received error:error]; + if (buf == NULL) { + return nil; + } + NSAssert(received == 1, @"expected 1 byte from readMaximum:"); + [data appendBytes:buf length:1]; + char c = buf[0]; + if (c == '\r') { + buf = [is readMaximum:1 length:&received error:error]; + if (buf == NULL) { + return nil; + } + NSAssert(received == 1, @"expected 1 byte from readMaximum:"); + [data appendBytes:buf length:1]; + c = buf[0]; + if (c == '\n') { + break; + } + } + } + NSString *line = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease]; + HSLogTrace(@"got line <%@>", [line substringToIndex:[line length] - 2]); + return line; +} ++ (NSString *)readLine:(FDInputStream *)is error:(NSError **)error { + NSMutableData *data = [[[NSMutableData alloc] init] autorelease]; + for (;;) { + NSUInteger received = 0; + NSError *myError = nil; + unsigned char *buf = [is readMaximum:1 length:&received error:&myError]; + if (buf == NULL) { + if ([myError code] != ERROR_EOF) { + if (error != NULL) { + *error = myError; + } + return nil; + } + //EOF. + break; + } + NSAssert(received == 1, @"expected 1 byte from readMaximum:"); + if (buf[0] == '\n') { + break; + } + [data appendBytes:buf length:1]; + } + return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease]; +} +@end diff --git a/io/IntegerIO.h b/io/IntegerIO.h new file mode 100644 index 0000000..baf7e57 --- /dev/null +++ b/io/IntegerIO.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "OutputStream.h" +#import "BufferedInputStream.h" + +@interface IntegerIO : NSObject { + +} ++ (void)writeInt32:(int32_t)i to:(NSMutableData *)data; ++ (void)writeUInt32:(uint32_t)i to:(NSMutableData *)data; + ++ (void)writeInt64:(int64_t)i to:(NSMutableData *)data; ++ (void)writeUInt64:(uint64_t)i to:(NSMutableData *)data; + ++ (BOOL)writeInt32:(int32_t)i to:(id )os error:(NSError **)error; ++ (BOOL)writeUInt32:(uint32_t)i to:(id )os error:(NSError **)error; + ++ (BOOL)writeInt64:(int64_t)i to:(id )os error:(NSError **)error; ++ (BOOL)writeUInt64:(uint64_t)i to:(id )os error:(NSError **)error; + ++ (BOOL)readInt32:(int32_t *)value from:(id )is error:(NSError **)error; ++ (BOOL)readUInt32:(uint32_t *)value from:(id )is error:(NSError **)error; + ++ (BOOL)readInt64:(int64_t *)value from:(id )is error:(NSError **)error; ++ (BOOL)readUInt64:(uint64_t *)value from:(id )is error:(NSError **)error; + +@end diff --git a/io/IntegerIO.m b/io/IntegerIO.m new file mode 100644 index 0000000..8b4b7ce --- /dev/null +++ b/io/IntegerIO.m @@ -0,0 +1,96 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "IntegerIO.h" +#import "InputStream.h" +#import "OutputStream.h" +#import "Streams.h" + +@implementation IntegerIO +// +// Big-endian network byte order. +// ++ (void)writeInt32:(int32_t)i to:(NSMutableData *)data { + [IntegerIO writeUInt32:(uint32_t)i to:data]; +} ++ (void)writeUInt32:(uint32_t)value to:(NSMutableData *)data { + uint32_t nboValue = OSSwapHostToBigInt32(value); + [data appendBytes:&nboValue length:4]; +} ++ (BOOL)writeInt32:(int32_t)value to:(id )os error:(NSError **)error { + return [IntegerIO writeUInt32:(uint32_t)value to:os error:error]; +} ++ (BOOL)writeUInt32:(uint32_t)value to:(id )os error:(NSError **)error { + uint32_t nboValue = OSSwapHostToBigInt32(value); + return [os write:(const unsigned char *)&nboValue length:4 error:error]; +} ++ (void)writeInt64:(int64_t)value to:(NSMutableData *)data { + [IntegerIO writeUInt64:(uint64_t)value to:data]; +} ++ (void)writeUInt64:(uint64_t)value to:(NSMutableData *)data { + uint64_t nboValue = OSSwapHostToBigInt64(value); + [data appendBytes:&nboValue length:8]; +} ++ (BOOL)writeInt64:(int64_t)value to:(id )os error:(NSError **)error { + return [IntegerIO writeUInt64:(uint64_t)value to:os error:error]; +} ++ (BOOL)writeUInt64:(uint64_t)value to:(id )os error:(NSError **)error { + uint64_t nboValue = OSSwapHostToBigInt64(value); + return [os write:(const unsigned char *)&nboValue length:8 error:error]; +} ++ (BOOL)readInt32:(int32_t *)value from:(id )is error:(NSError **)error { + return [IntegerIO readUInt32:(uint32_t *)value from:is error:error]; +} ++ (BOOL)readUInt32:(uint32_t *)value from:(id )is error:(NSError **)error { + *value = 0; + unsigned char *buf = [is readExactly:4 error:error]; + if (!buf) { + return NO; + } + uint32_t *nboValue = (uint32_t *)buf; + *value = OSSwapBigToHostInt32(*nboValue); + return YES; +} ++ (BOOL)readInt64:(int64_t *)value from:(id )is error:(NSError **)error { + return [IntegerIO readUInt64:(uint64_t *)value from:is error:error]; +} ++ (BOOL)readUInt64:(uint64_t *)value from:(id )is error:(NSError **)error { + *value = 0; + unsigned char *buf = [is readExactly:8 error:error]; + if (!buf) { + return NO; + } + uint64_t *nboValue = (uint64_t *)buf; + *value = OSSwapBigToHostInt64(*nboValue); + return YES; +} +@end diff --git a/io/NSData-InputStream.h b/io/NSData-InputStream.h new file mode 100644 index 0000000..5e27f68 --- /dev/null +++ b/io/NSData-InputStream.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class DataInputStream; + +@interface NSData (InputStream) +- (DataInputStream *)newInputStream; +@end diff --git a/io/NSData-InputStream.m b/io/NSData-InputStream.m new file mode 100644 index 0000000..87a6aa2 --- /dev/null +++ b/io/NSData-InputStream.m @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "NSData-InputStream.h" +#import "DataInputStream.h" + +@implementation NSData (InputStream) +- (DataInputStream *)newInputStream { + return [[DataInputStream alloc] initWithData:self]; +} +@end + diff --git a/io/NSFileManager_extra.h b/io/NSFileManager_extra.h new file mode 100644 index 0000000..be5a05e --- /dev/null +++ b/io/NSFileManager_extra.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface NSFileManager (extra) +- (BOOL)ensureParentPathExistsForPath:(NSString *)path error:(NSError **)error; +@end diff --git a/io/NSFileManager_extra.m b/io/NSFileManager_extra.m new file mode 100644 index 0000000..267860c --- /dev/null +++ b/io/NSFileManager_extra.m @@ -0,0 +1,55 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#import "NSFileManager_extra.h" +#import "SetNSError.h" +#import "FileInputStream.h" +#import "EncryptedInputStream.h" +#import "Streams.h" + +@implementation NSFileManager (extra) +- (BOOL)ensureParentPathExistsForPath:(NSString *)path error:(NSError **)error { + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *parentPath = [path stringByDeletingLastPathComponent]; + BOOL isDirectory = NO; + if ([fm fileExistsAtPath:parentPath isDirectory:&isDirectory]) { + if (!isDirectory) { + SETNSERROR(@"FileErrorDomain", -1, @"parent path %@ exists and is not a directory", parentPath); + return NO; + } + } else if (![fm createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:error]) { + return NO; + } + return YES; +} +@end diff --git a/io/OutputStream.h b/io/OutputStream.h new file mode 100644 index 0000000..39db911 --- /dev/null +++ b/io/OutputStream.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@protocol OutputStream +- (BOOL)write:(const unsigned char *)buf length:(NSUInteger)len error:(NSError **)error; +- (unsigned long long)bytesWritten; +@end diff --git a/io/StreamPair.h b/io/StreamPair.h new file mode 100644 index 0000000..a3c9bad --- /dev/null +++ b/io/StreamPair.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" +#import "OutputStream.h" + +@protocol StreamPair +- (void)setCloseRequested; +- (BOOL)isUsable; + +@end diff --git a/io/StreamPairFactory.h b/io/StreamPairFactory.h new file mode 100644 index 0000000..6817f76 --- /dev/null +++ b/io/StreamPairFactory.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@protocol StreamPair; + +@interface StreamPairFactory : NSObject { + NSTimeInterval maxStreamPairLifetime; + NSLock *lock; + NSMutableDictionary *threadMap; + +} ++ (StreamPairFactory *)theFactory; +- (void)setMaxStreamPairLifetime:(NSTimeInterval)theMaxLifetime; +- (id )newStreamPairToHost:(NSString *)theHost useSSL:(BOOL)isUseSSL error:(NSError **)error; + +@end diff --git a/io/StreamPairFactory.m b/io/StreamPairFactory.m new file mode 100644 index 0000000..787a275 --- /dev/null +++ b/io/StreamPairFactory.m @@ -0,0 +1,168 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "StreamPairFactory.h" +#import "CFStreamPair.h" + +static StreamPairFactory *theFactory = nil; + +#define DEFAULT_MAX_STREAM_PAIR_LIFETIME_SECONDS (60) +#define CLEANUP_THREAD_SLEEP_SECONDS (5) + +@interface StreamPairMap : NSObject { + NSMutableDictionary *streamPairs; +} +- (id )newStreamPairToHost:(NSString *)host useSSL:(BOOL)useSSL maxLifeTimeSeconds:(NSTimeInterval)theMaxLifetime error:(NSError **)error; +- (void)dropUnusableStreamPairs; +@end + +@implementation StreamPairMap +- (id)init { + if (self = [super init]) { + streamPairs = [[NSMutableDictionary alloc] init]; + } + return self; +} +- (void)dealloc { + [streamPairs release]; + [super dealloc]; +} +- (id )newStreamPairToHost:(NSString *)host useSSL:(BOOL)useSSL maxLifeTimeSeconds:(NSTimeInterval)theMaxLifetime error:(NSError **)error { + NSString *key = [NSString stringWithFormat:@"%@:%@", [host lowercaseString], (useSSL ? @"SSL" : @"noSSL")]; + id streamPair = [streamPairs objectForKey:key]; + if (streamPair != nil) { + if (![streamPair isUsable]) { + [streamPairs removeObjectForKey:key]; + streamPair = nil; + } else { + [streamPair retain]; + } + } + if (streamPair == nil) { + streamPair = [[CFStreamPair alloc] initWithHost:host useSSL:useSSL maxLifetime:theMaxLifetime]; + [streamPairs setObject:streamPair forKey:key]; + } + return streamPair; +} +- (void)dropUnusableStreamPairs { + NSMutableArray *keysToDrop = [NSMutableArray array]; + for (NSString *key in streamPairs) { + id streamPair = [streamPairs objectForKey:key]; + if (![streamPair isUsable]) { + [keysToDrop addObject:key]; + } + } + [streamPairs removeObjectsForKeys:keysToDrop]; +} +@end + + + +@implementation StreamPairFactory ++ (StreamPairFactory *)theFactory { + if (theFactory == nil) { + theFactory = [[super allocWithZone:NULL] init]; + } + return theFactory; +} + +/* Singleton recipe: */ ++ (id)allocWithZone:(NSZone *)zone { + return [[StreamPairFactory theFactory] retain]; +} +- (id)copyWithZone:(NSZone *)zone { + return self; +} +- (id)retain { + return self; +} +- (NSUInteger)retainCount { + return NSUIntegerMax; //denotes an object that cannot be released +} +- (void)release { + //do nothing +} +- (id)autorelease { + return self; +} + +- (id)init { + if (self = [super init]) { + lock = [[NSLock alloc] init]; + [lock setName:@"SocketFactory lock"]; + threadMap = [[NSMutableDictionary alloc] init]; + maxStreamPairLifetime = DEFAULT_MAX_STREAM_PAIR_LIFETIME_SECONDS; + [NSThread detachNewThreadSelector:@selector(dropUnusableSockets) toTarget:self withObject:nil]; + } + return self; +} +- (void)dealloc { + [lock release]; + [threadMap release]; + [super dealloc]; +} +- (void)setMaxStreamPairLifetime:(NSTimeInterval)theMaxLifetime { + maxStreamPairLifetime = theMaxLifetime; +} +- (id )newStreamPairToHost:(NSString *)theHost useSSL:(BOOL)isUseSSL error:(NSError **)error { + void *pthreadPtr = pthread_self(); +#ifdef __LP64__ + NSNumber *threadID = [NSNumber numberWithUnsignedLongLong:(uint64_t)pthreadPtr]; +#else + NSNumber *threadID = [NSNumber numberWithUnsignedLong:(uint32_t)pthreadPtr]; +#endif + [lock lock]; + StreamPairMap *map = [threadMap objectForKey:threadID]; + if (map == nil) { + map = [[StreamPairMap alloc] init]; + [threadMap setObject:map forKey:threadID]; + [map release]; + } + id streamPair = [map newStreamPairToHost:theHost useSSL:isUseSSL maxLifeTimeSeconds:maxStreamPairLifetime error:error]; + [lock unlock]; + return streamPair; +} + +#pragma mark cleanup thread +- (void)dropUnusableSockets { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + for (;;) { + [NSThread sleepForTimeInterval:CLEANUP_THREAD_SLEEP_SECONDS]; + [lock lock]; + for (StreamPairMap *map in [threadMap allValues]) { + [map dropUnusableStreamPairs]; + } + [lock unlock]; + } + [pool drain]; +} +@end diff --git a/io/Streams.h b/io/Streams.h new file mode 100644 index 0000000..47c654b --- /dev/null +++ b/io/Streams.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@protocol InputStream; +@protocol OutputStream; + +@interface Streams : NSObject { + +} ++ (BOOL)transferFrom:(id )is to:(id )os error:(NSError **)error; ++ (BOOL)transferFrom:(id )is atomicallyToFile:(NSString *)path bytesWritten:(unsigned long long *)written error:(NSError **)error; +@end diff --git a/io/Streams.m b/io/Streams.m new file mode 100644 index 0000000..d5d1681 --- /dev/null +++ b/io/Streams.m @@ -0,0 +1,91 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "Streams.h" +#import "SetNSError.h" +#import "FDOutputStream.h" +#import "NSErrorCodes.h" +#import "InputStream.h" + +@implementation Streams ++ (BOOL)transferFrom:(id )is to:(id )os error:(NSError **)error { + BOOL ret = YES; + for (;;) { + NSUInteger received; + NSError *readError = nil; + unsigned char *buf = [is read:&received error:&readError]; + if (buf == NULL) { + if ([readError code] != ERROR_EOF) { + ret = NO; + if (error != NULL) { + *error = readError; + } + } + break; + } + NSAssert(received > 0, @"expected to receive more than 0 bytes"); + if (![os write:buf length:received error:error]) { + ret = NO; + break; + } + } + return ret; +} ++ (BOOL)transferFrom:(id )is atomicallyToFile:(NSString *)path bytesWritten:(unsigned long long *)written error:(NSError **)error { + NSString *tempFileTemplate = [path stringByAppendingString:@".XXXXXX"]; + const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation]; + char *tempFileCString = strdup(tempFileTemplateCString); + int fd = mkstemp(tempFileCString); + NSString *tempFile = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempFileCString length:strlen(tempFileCString)]; + free(tempFileCString); + if (fd == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"mkstemp(%s): %s", tempFileCString, strerror(errno)); + return NO; + } + FDOutputStream *fos = [[FDOutputStream alloc] initWithFD:fd]; + BOOL ret = [Streams transferFrom:is to:fos error:error]; + if (ret) { + if (rename([tempFile fileSystemRepresentation], [path fileSystemRepresentation]) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"rename(%@, %@): %s", tempFile, path, strerror(errno)); + ret = NO; + } + } + if (ret) { + *written = [fos bytesWritten]; + } else { + HSLogError(@"error transferring bytes to %@", path); + } + [fos release]; + close(fd); + return ret; +} +@end diff --git a/io/StringIO.h b/io/StringIO.h new file mode 100644 index 0000000..ab1abb6 --- /dev/null +++ b/io/StringIO.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "BufferedInputStream.h" +#import "OutputStream.h" + +@interface StringIO : NSObject { + +} ++ (void)write:(NSString *)value to:(NSMutableData *)data; ++ (BOOL)write:(NSString *)str to:(id )os error:(NSError **)error; ++ (BOOL)read:(NSString **)value from:(id )is error:(NSError **)error; +@end diff --git a/io/StringIO.m b/io/StringIO.m new file mode 100644 index 0000000..7bc1979 --- /dev/null +++ b/io/StringIO.m @@ -0,0 +1,78 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "IntegerIO.h" +#import "StringIO.h" +#import "InputStream.h" +#import "DataInputStream.h" +#import "Streams.h" +#import "NSData-InputStream.h" +#import "BooleanIO.h" + +@implementation StringIO ++ (void)write:(NSString *)str to:(NSMutableData *)data { + [BooleanIO write:(str != nil) to:data]; + if (str != nil) { + const char *utf8 = [str UTF8String]; + uint64_t len = (uint64_t)strlen(utf8); + [IntegerIO writeUInt64:len to:data]; + [data appendBytes:utf8 length:len]; + } +} ++ (BOOL)write:(NSString *)str to:(id )os error:(NSError **)error { + //FIXME: This is really inefficient! + NSMutableData *data = [[NSMutableData alloc] init]; + [StringIO write:str to:data]; + BOOL ret = [os write:[data bytes] length:[data length] error:error]; + [data release]; + return ret; +} ++ (BOOL)read:(NSString **)value from:(id )is error:(NSError **)error { + *value = nil; + BOOL isNotNil = NO; + if (![BooleanIO read:&isNotNil from:is error:error]) { + return NO; + } + if (isNotNil) { + uint64_t len; + if (![IntegerIO readUInt64:&len from:is error:error]) { + return NO; + } + unsigned char *utf8 = [is readExactly:len error:error]; + if (!utf8) { + return NO; + } + *value = [[[NSString alloc] initWithBytes:utf8 length:len encoding:NSUTF8StringEncoding] autorelease]; + } + return YES; +} +@end diff --git a/io/Writer.h b/io/Writer.h new file mode 100644 index 0000000..a27bf41 --- /dev/null +++ b/io/Writer.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "OutputStream.h" + +@interface Writer : NSObject { + id os; +} +- (id)initWithOutputStream:(id )theOS; +- (BOOL)write:(NSString *)text error:(NSError **)error; +@end diff --git a/io/Writer.m b/io/Writer.m new file mode 100644 index 0000000..f0211ef --- /dev/null +++ b/io/Writer.m @@ -0,0 +1,52 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "Writer.h" + +@implementation Writer +- (id)initWithOutputStream:(id )theOS { + if (self = [super init]) { + os = [theOS retain]; + } + return self; +} +- (void)dealloc { + [os release]; + [super dealloc]; +} +- (BOOL)write:(NSString *)text error:(NSError **)error { + NSAssert(text != nil, @"text must not be nil"); + const char *utf8 = [text UTF8String]; + return [os write:(const unsigned char *)utf8 length:strlen(utf8) error:error]; +} + +@end diff --git a/plist/ArrayNode.h b/plist/ArrayNode.h new file mode 100644 index 0000000..5aeb318 --- /dev/null +++ b/plist/ArrayNode.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "PListNode.h" +@class ArrayNode; +@class BooleanNode; +@class DictNode; +@class IntegerNode; +@class RealNode; +@class StringNode; + + +@interface ArrayNode : NSObject { + NSMutableArray *list; +} +- (id)initWithArray:(NSArray *)nodes; +- (NSUInteger)size; +- (int)arrayElementsType; +- (id )objectAtIndex:(int)index; +- (ArrayNode *)arrayNodeAtIndex:(int)index; +- (BooleanNode *)booleanNodeAtIndex:(int)index; +- (DictNode *)dictNodeAtIndex:(int)index; +- (IntegerNode *)integerNodeAtIndex:(int)index; +- (RealNode *)realNodeAtIndex:(int)index; +- (StringNode *)stringNodeAtIndex:(int)index; +- (void)add:(id )node; +- (void)add:(id )node atIndex:(int)index; +@end diff --git a/plist/ArrayNode.m b/plist/ArrayNode.m new file mode 100644 index 0000000..4c7fa21 --- /dev/null +++ b/plist/ArrayNode.m @@ -0,0 +1,106 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "DictNode.h" +#import "ArrayNode.h" +#import "PListNodeType.h" + +@implementation ArrayNode +- (id)init { + if (self = [super init]) { + list = [[NSMutableArray alloc] init]; + } + return self; +} +- (id)initWithArray:(NSArray *)nodes { + if (self = [super init]) { + list = [[NSMutableArray alloc] initWithArray:nodes]; + } + return self; +} +- (void)dealloc { + [list release]; + [super dealloc]; +} +- (NSUInteger)size { + return [list count]; +} +- (int)arrayElementsType { + if ([list count] == 0) { + // Who knows. + return PLN_STRING; + } + id node = [list objectAtIndex:0]; + return [node type]; +} +- (id )objectAtIndex:(int)index { + return [list objectAtIndex:index]; +} +- (ArrayNode *)arrayNodeAtIndex:(int)index { + return [list objectAtIndex:index]; +} +- (BooleanNode *)booleanNodeAtIndex:(int)index { + return [list objectAtIndex:index]; +} +- (DictNode *)dictNodeAtIndex:(int)index { + return [list objectAtIndex:index]; +} +- (IntegerNode *)integerNodeAtIndex:(int)index { + return [list objectAtIndex:index]; +} +- (RealNode *)realNodeAtIndex:(int)index { + return [list objectAtIndex:index]; +} +- (StringNode *)stringNodeAtIndex:(int)index { + return [list objectAtIndex:index]; +} +- (void)add:(id )node { + [list addObject:node]; +} +- (void)add:(id )node atIndex:(int)index { + [list insertObject:node atIndex:index]; +} + + +#pragma mark PListNode protocol + +- (int)type { + return PLN_ARRAY; +} + + +#pragma mark NSObject protocol + +- (NSString *)description { + return [NSString stringWithFormat:@"", self, [list description]]; +} +@end diff --git a/plist/BooleanNode.h b/plist/BooleanNode.h new file mode 100644 index 0000000..87d1a21 --- /dev/null +++ b/plist/BooleanNode.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "PListNode.h" + +@interface BooleanNode : NSObject { + BOOL value; +} +- (id)initWithBoolean:(BOOL)value; +- (BOOL)booleanValue; +@end diff --git a/plist/BooleanNode.m b/plist/BooleanNode.m new file mode 100644 index 0000000..113ef54 --- /dev/null +++ b/plist/BooleanNode.m @@ -0,0 +1,61 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PListNodeType.h" +#import "BooleanNode.h" + + +@implementation BooleanNode +- (id)initWithBoolean:(BOOL)b { + if (self = [super init]) { + value = b; + } + return self; +} +- (BOOL)booleanValue { + return value; +} + + +#pragma mark PListNode protocol + +- (int)type { + return PLN_BOOLEAN; +} + + +#pragma mark NSObject protocol + +- (NSString *)description { + return [NSString stringWithFormat:@"", self, (value ? @"YES" : @"NO")]; +} +@end diff --git a/plist/DictNode.h b/plist/DictNode.h new file mode 100644 index 0000000..0162b45 --- /dev/null +++ b/plist/DictNode.h @@ -0,0 +1,73 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class ArrayNode; +@class BooleanNode; +@class DictNode; +@class IntegerNode; +@class RealNode; +@class StringNode; +#import "PListNode.h" + +@interface DictNode : NSObject { + NSMutableDictionary *dict; + NSMutableArray *orderedKeys; +} ++ (DictNode *)dictNodeWithContentsOfXMLFile:(NSString *)path error:(NSError **)error; ++ (DictNode *)dictNodeWithXMLData:(NSData *)data error:(NSError **)error; + +- (int)size; +- (BOOL)containsKey:(NSString *)key; +- (NSArray *)keySet; +- (NSArray *)orderedKeySet; +- (NSArray *)values; +- (int)nodeTypeForKey:(NSString *)key; +- (id )nodeForKey:(NSString *)key; +- (ArrayNode *)arrayNodeForKey:(NSString *)key; +- (BooleanNode *)booleanNodeForKey:(NSString *)key; +- (DictNode *)dictNodeForKey:(NSString *)key; +- (IntegerNode *)integerNodeForKey:(NSString *)key; +- (RealNode *)realNodeForKey:(NSString *)key; +- (StringNode *)stringNodeForKey:(NSString *)key; +- (void)putString:(NSString *)value forKey:(NSString *)key; +- (void)putInt:(int)value forKey:(NSString *)key; +- (void)putLongLong:(long long)value forKey:(NSString *)key; +- (void)putBoolean:(BOOL)value forKey:(NSString *)key; +- (void)putDouble:(double)value forKey:(NSString *)key; +- (void)put:(id )value forKey:(NSString *)key; +- (void)removeKey:(NSString *)key; +- (void)removeAllObjects; + +- (BOOL)writeXMLToFile:(NSString *)path error:(NSError **)error; +- (NSData *)XMLData; +@end diff --git a/plist/DictNode.m b/plist/DictNode.m new file mode 100644 index 0000000..dcfb6b5 --- /dev/null +++ b/plist/DictNode.m @@ -0,0 +1,188 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PListNode.h" +#import "PListNodeType.h" +#import "ArrayNode.h" +#import "BooleanNode.h" +#import "IntegerNode.h" +#import "RealNode.h" +#import "StringNode.h" +#import "DictNode.h" +#import "XMLPListReader.h" +#import "XMLPListWriter.h" + +@implementation DictNode ++ (DictNode *)dictNodeWithContentsOfXMLFile:(NSString *)path error:(NSError **)error { + NSData *data = [[NSData alloc] initWithContentsOfFile:path options:0 error:error]; + if (!data) { + return nil; + } + DictNode *dn = [DictNode dictNodeWithXMLData:data error:error]; + [data release]; + return dn; +} ++ (DictNode *)dictNodeWithXMLData:(NSData *)data error:(NSError **)error { + XMLPListReader *reader = [[XMLPListReader alloc] initWithData:data]; + DictNode *dn = [reader read:error]; + [reader release]; + return dn; +} + +- (id)init { + if (self = [super init]) { + dict = [[NSMutableDictionary alloc] init]; + orderedKeys = [[NSMutableArray alloc] init]; + } + return self; +} +- (void)dealloc { + [dict release]; + [orderedKeys release]; + [super dealloc]; +} +- (int)size { + return [dict count]; +} +- (BOOL)containsKey:(NSString *)key { + return [dict objectForKey:key] != nil; +} +- (NSArray *)keySet { + return [dict allKeys]; +} +- (NSArray *)orderedKeySet { + return orderedKeys; +} +- (NSArray *)values { + return [dict allValues]; +} +- (int)nodeTypeForKey:(NSString *)key { + id node = [dict objectForKey:key]; + return [node type]; +} +- (id )nodeForKey:(NSString *)key { + return [dict objectForKey:key]; +} +- (ArrayNode *)arrayNodeForKey:(NSString *)key { + return [dict objectForKey:key]; +} +- (BooleanNode *)booleanNodeForKey:(NSString *)key { + return [dict objectForKey:key]; +} +- (DictNode *)dictNodeForKey:(NSString *)key { + return [dict objectForKey:key]; +} +- (IntegerNode *)integerNodeForKey:(NSString *)key { + return [dict objectForKey:key]; +} +- (RealNode *)realNodeForKey:(NSString *)key { + return [dict objectForKey:key]; +} +- (StringNode *)stringNodeForKey:(NSString *)key { + return [dict objectForKey:key]; +} +- (void)putString:(NSString *)value forKey:(NSString *)key { + StringNode *sn = [[StringNode alloc] initWithString:value]; + [self put:sn forKey:key]; + [sn release]; +} +- (void)putInt:(int)value forKey:(NSString *)key { + IntegerNode *in = [[IntegerNode alloc] initWithInt:value]; + [self put:in forKey:key]; + [in release]; +} +- (void)putLongLong:(long long)value forKey:(NSString *)key { + IntegerNode *in = [[IntegerNode alloc] initWithLongLong:value]; + [self put:in forKey:key]; + [in release]; +} +- (void)putBoolean:(BOOL)value forKey:(NSString *)key { + BooleanNode *bn = [[BooleanNode alloc] initWithBoolean:value]; + [self put:bn forKey:key]; + [bn release]; +} +- (void)putDouble:(double)value forKey:(NSString *)key { + RealNode *rn = [[RealNode alloc] initWithDouble:value]; + [self put:rn forKey:key]; + [rn release]; +} +- (void)put:(id )value forKey:(NSString *)key { + [dict setObject:value forKey:key]; + NSUInteger index = [orderedKeys indexOfObject:key]; + if (index != NSNotFound) { + [orderedKeys removeObjectAtIndex:index]; + } + [orderedKeys addObject:key]; +} +- (void)removeKey:(NSString *)key { + [dict removeObjectForKey:key]; + NSUInteger index = [orderedKeys indexOfObject:key]; + if (index != NSNotFound) { + [orderedKeys removeObjectAtIndex:index]; + } +} +- (void)removeAllObjects { + [dict removeAllObjects]; + [orderedKeys removeAllObjects]; +} + +- (BOOL)writeXMLToFile:(NSString *)path error:(NSError **)error { + NSMutableData *data = [[NSMutableData alloc] init]; + XMLPListWriter *writer = [[XMLPListWriter alloc] initWithMutableData:data]; + [writer write:self]; + [writer release]; + BOOL ret = [data writeToFile:path options:NSAtomicWrite error:error]; + [data release]; + return ret; +} +- (NSData *)XMLData { + NSMutableData *data = [NSMutableData data]; + XMLPListWriter *writer = [[XMLPListWriter alloc] initWithMutableData:data]; + [writer write:self]; + [writer release]; + return data; +} + + +#pragma mark PListNode protocol + +- (int)type { + return PLN_DICT; +} + + +#pragma mark NSObject protocol + +- (NSString *)description { + return [NSString stringWithFormat:@"", self, [dict description]]; +} +@end diff --git a/plist/IntegerNode.h b/plist/IntegerNode.h new file mode 100644 index 0000000..267764c --- /dev/null +++ b/plist/IntegerNode.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "PListNode.h" + +@interface IntegerNode : NSObject { + long long value; +} +- (id)initWithInt:(int)theValue; +- (id)initWithString:(NSString *)theValue error:(NSError **)error; +- (id)initWithLongLong:(long long)theValue; +- (int)intValue; +- (long long)longlongValue; +@end diff --git a/plist/IntegerNode.m b/plist/IntegerNode.m new file mode 100644 index 0000000..7cc82ea --- /dev/null +++ b/plist/IntegerNode.m @@ -0,0 +1,83 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PListNode.h" +#import "PListNodeType.h" +#import "IntegerNode.h" +#import "SetNSError.h" + +@implementation IntegerNode +- (id)initWithInt:(int)theValue { + if (self = [super init]) { + value = (long long)theValue; + } + return self; +} +- (id)initWithString:(NSString *)theValue error:(NSError **)error { + if (self = [super init]) { + NSScanner *scanner = [NSScanner scannerWithString:theValue]; + if (![scanner scanLongLong:&value]) { + SETNSERROR(@"PListErrorDomain", -1, @"string does not contain a long long: %@", theValue); + [self release]; + self = nil; + } + } + return self; +} +- (id)initWithLongLong:(long long)theValue { + if (self = [super init]) { + value = theValue; + } + return self; +} +- (int)intValue { + return (int)value; +} +- (long long)longlongValue { + return value; +} + + +#pragma mark PListNode protocol + +- (int)type { + return PLN_INTEGER; +} + + +#pragma mark NSObject protocol + +- (NSString *)description { + return [NSString stringWithFormat:@"", self, value]; +} + +@end diff --git a/plist/PListNode.h b/plist/PListNode.h new file mode 100644 index 0000000..6fe7f08 --- /dev/null +++ b/plist/PListNode.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@protocol PListNode +- (int)type; +@end diff --git a/plist/PListNodeType.h b/plist/PListNodeType.h new file mode 100644 index 0000000..944b543 --- /dev/null +++ b/plist/PListNodeType.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +enum { + PLN_INTEGER = 1, + PLN_STRING = 2, + PLN_BOOLEAN = 4, + PLN_REAL = 8, + PLN_ARRAY = 16, + PLN_DICT = 32 +}; diff --git a/plist/RealNode.h b/plist/RealNode.h new file mode 100644 index 0000000..11e9b52 --- /dev/null +++ b/plist/RealNode.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "PListNode.h" + +@interface RealNode : NSObject { + double value; +} +- (id)initWithDouble:(double)value; +- (id)initWithString:(NSString *)theValue error:(NSError **)error; +- (double)doubleValue; +@end diff --git a/plist/RealNode.m b/plist/RealNode.m new file mode 100644 index 0000000..1eab58d --- /dev/null +++ b/plist/RealNode.m @@ -0,0 +1,72 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PListNodeType.h" +#import "RealNode.h" +#import "SetNSError.h" + +@implementation RealNode +- (id)initWithDouble:(double)theValue { + if (self = [super init]) { + value = theValue; + } + return self; +} +- (id)initWithString:(NSString *)theValue error:(NSError **)error { + if (self = [super init]) { + NSScanner *scanner = [NSScanner scannerWithString:theValue]; + if (![scanner scanDouble:&value]) { + SETNSERROR(@"PListErrorDomain", -1, @"string does not contain a double: %@", theValue); + [self release]; + self = nil; + } + } + return self; +} +- (double)doubleValue { + return value; +} + + +#pragma mark PListNode protocol + +- (int)type { + return PLN_REAL; +} + + +#pragma mark NSObject protocol + +- (NSString *)description { + return [NSString stringWithFormat:@"", self, value]; +} +@end diff --git a/plist/StringNode.h b/plist/StringNode.h new file mode 100644 index 0000000..04efe4d --- /dev/null +++ b/plist/StringNode.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "PListNode.h" + +@interface StringNode : NSObject { + NSString *value; +} +- (id)initWithString:(NSString *)theValue; +- (NSString *)stringValue; +@end diff --git a/plist/StringNode.m b/plist/StringNode.m new file mode 100644 index 0000000..94612a0 --- /dev/null +++ b/plist/StringNode.m @@ -0,0 +1,66 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PListNodeType.h" +#import "StringNode.h" + + +@implementation StringNode +- (id)initWithString:(NSString *)theValue { + if (self = [super init]) { + value = [theValue copy]; + } + return self; +} +- (void)dealloc { + [value release]; + [super dealloc]; +} +- (NSString *)stringValue { + return value; +} + + +#pragma mark PListNode protocol + +- (int)type { + return PLN_STRING; +} + + + +#pragma mark NSObject protocol + +- (NSString *)description { + return [NSString stringWithFormat:@"", self, value]; +} +@end diff --git a/plist/XMLPListReader.h b/plist/XMLPListReader.h new file mode 100644 index 0000000..8e6ca3a --- /dev/null +++ b/plist/XMLPListReader.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class DictNode; + +@interface XMLPListReader : NSObject { + NSData *data; +} +- (id)initWithData:(NSData *)theData; +- (DictNode *)read:(NSError **)error; +@end diff --git a/plist/XMLPListReader.m b/plist/XMLPListReader.m new file mode 100644 index 0000000..39d1538 --- /dev/null +++ b/plist/XMLPListReader.m @@ -0,0 +1,168 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "ArrayNode.h" +#import "BooleanNode.h" +#import "DictNode.h" +#import "IntegerNode.h" +#import "RealNode.h" +#import "StringNode.h" +#import "PListNode.h" +#import "PListNodeType.h" +#import "XMLPListReader.h" +#import "SetNSError.h" + +@interface XMLPListReader (internal) +- (ArrayNode *)readArray:(NSXMLNode *)elem error:(NSError **)error; +- (DictNode *)readDict:(NSXMLNode *)elem error:(NSError **)error; +- (id )makeNode:(NSXMLNode *)child error:(NSError **)error; +@end + + +@implementation XMLPListReader +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + data = [theData retain]; + } + return self; +} +- (void)dealloc { + [data release]; + [super dealloc]; +} +- (DictNode *)read:(NSError **)error { + NSXMLDocument *doc = [[NSXMLDocument alloc] initWithData:data options:0 error:error]; + if (!doc) { + return nil; + } + DictNode *dn = nil; + NSXMLElement *rootElem = [doc rootElement]; + if (![[rootElem name] isEqualToString:@"plist"]) { + SETNSERROR(@"PListErrorDomain", -1, @"expected root element 'plist'"); + [doc release]; + return nil; + } + ArrayNode *an = [self readArray:rootElem error:error]; + if (!an) { + [doc release]; + return nil; + } + if ([an size] != 1) { + SETNSERROR(@"PListErrorDomain", -1, @"empty root array in PList"); + [doc release]; + return nil; + } + if ([an arrayElementsType] != PLN_DICT) { + SETNSERROR(@"PListErrorDomain", -1, @"expected root array in PList"); + [doc release]; + return nil; + } + dn = (DictNode *)[an objectAtIndex:0]; + [doc release]; + return dn; +} +@end + +@implementation XMLPListReader (internal) +- (ArrayNode *)readArray:(NSXMLNode *)elem error:(NSError **)error { + NSMutableArray *nodes = [[NSMutableArray alloc] init]; + NSArray *children = [elem children]; + for (NSXMLNode *childNode in children) { + if ([childNode kind] == NSXMLTextKind) { + // Skip. + } else { + id node = [self makeNode:childNode error:error]; + if (!node) { + [nodes release]; + return NO; + } + [nodes addObject:node]; + } + } + ArrayNode *ret = [[[ArrayNode alloc] initWithArray:nodes] autorelease]; + [nodes release]; + return ret; +} +- (DictNode *)readDict:(NSXMLNode *)elem error:(NSError **)error { + DictNode *dn = [[[DictNode alloc] init] autorelease]; + NSArray *children = [elem children]; + NSString *key = nil; + for (NSXMLNode *childNode in children) { + if ([childNode kind] == NSXMLTextKind) { + // Skip. + } else { + NSString *childNodeName = [childNode name]; + if ([childNodeName isEqualToString:@"key"]) { + key = [childNode stringValue]; + } else { + id node = [self makeNode:childNode error:error]; + if (!node) { + return NO; + } + NSAssert(key != nil, @"must have key before adding value"); + [dn put:node forKey:key]; + } + } + } + return dn; +} +- (id )makeNode:(NSXMLNode *)child error:(NSError **)error { + NSString *childName = [child name]; + id ret = nil; + if ([childName isEqualToString:@"array"]) { + ret = [self readArray:child error:error]; + } else if ([childName isEqualToString:@"dict"]) { + ret = [self readDict:child error:error]; + } else if ([childName isEqualToString:@"true"]) { + ret = [[[BooleanNode alloc] initWithBoolean:YES] autorelease]; + } else if ([childName isEqualToString:@"false"]) { + ret = [[[BooleanNode alloc] initWithBoolean:NO] autorelease]; + } else if ([childName isEqualToString:@"integer"]) { + IntegerNode *node = [[[IntegerNode alloc] initWithString:[child stringValue] error:error] autorelease]; + if (node) { + ret = node; + } + } else if ([childName isEqualToString:@"real"]) { + RealNode *node = [[[RealNode alloc] initWithString:[child stringValue] error:error] autorelease]; + if (node) { + ret = node; + } + } else if ([childName isEqualToString:@"string"]) { + NSString *value = (NSString *)CFXMLCreateStringByUnescapingEntities(kCFAllocatorDefault, (CFStringRef)[child stringValue], NULL); + ret = [[[StringNode alloc] initWithString:value] autorelease]; + [value release]; + } else { + SETNSERROR(@"PListErrorDomain", -1, @"invalid node type"); + } + return ret; +} +@end diff --git a/plist/XMLPListWriter.h b/plist/XMLPListWriter.h new file mode 100644 index 0000000..4150b18 --- /dev/null +++ b/plist/XMLPListWriter.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class DictNode; + +@interface XMLPListWriter : NSObject { + NSMutableData *data; +} +- (id)initWithMutableData:(NSMutableData *)theData; +- (void)write:(DictNode *)plist; +@end diff --git a/plist/XMLPListWriter.m b/plist/XMLPListWriter.m new file mode 100644 index 0000000..386a6cd --- /dev/null +++ b/plist/XMLPListWriter.m @@ -0,0 +1,163 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "ArrayNode.h" +#import "BooleanNode.h" +#import "DictNode.h" +#import "IntegerNode.h" +#import "RealNode.h" +#import "StringNode.h" +#import "PListNode.h" +#import "PListNodeType.h" +#import "XMLPListWriter.h" + +#define TAB @"\t" + +@interface XMLPListWriter (internal) +- (void)writeArray:(ArrayNode *)node toElement:(NSXMLElement *)elem; +- (void)writeBoolean:(BooleanNode *)node toElement:(NSXMLElement *)elem; +- (void)writeDict:(DictNode *)node toElement:(NSXMLElement *)elem; +- (void)writeInteger:(IntegerNode *)node toElement:(NSXMLElement *)elem; +- (void)writeReal:(RealNode *)node toElement:(NSXMLElement *)elem; +- (void)writeString:(StringNode *)node toElement:(NSXMLElement *)elem; +- (void)writePListNode:(id )node toElement:(NSXMLElement *)elem; +@end + +@implementation XMLPListWriter +- (id)initWithMutableData:(NSMutableData *)theData { + if (self = [super init]) { + data = [theData retain]; + } + return self; +} +- (void)dealloc { + [data release]; + [super dealloc]; +} + +- (void)write:(DictNode *)plist { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSXMLElement *rootElem = [[NSXMLElement alloc] initWithName:@"plist"]; + NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init]; + [attributes setObject:@"1.0" forKey:@"version"]; + [rootElem setAttributesAsDictionary:attributes]; + [attributes release]; + [self writeDict:plist toElement:rootElem]; + NSXMLDocument *doc = [[NSXMLDocument alloc] initWithRootElement:rootElem]; + [rootElem release]; + NSString *xmlString = [doc XMLStringWithOptions:NSXMLNodePrettyPrint]; + [doc release]; + [data appendData:[xmlString dataUsingEncoding:NSUTF8StringEncoding]]; + [pool drain]; +} + +@end + +@implementation XMLPListWriter (internal) +- (void)writeArray:(ArrayNode *)node toElement:(NSXMLElement *)elem { + NSXMLElement *arrayElem = [[NSXMLElement alloc] initWithName:@"array"]; + int size = [node size]; + for (int i = 0; i < size; i++) { + [self writePListNode:[node objectAtIndex:i] toElement:arrayElem]; + } + [elem addChild:arrayElem]; + [arrayElem release]; +} +- (void)writeBoolean:(BooleanNode *)node toElement:(NSXMLElement *)elem { + NSXMLElement *childElem; + if ([node booleanValue]) { + childElem = [[NSXMLElement alloc] initWithName:@"true"]; + } else { + childElem = [[NSXMLElement alloc] initWithName:@"false"]; + } + [elem addChild:childElem]; + [childElem release]; +} +- (void)writeDict:(DictNode *)node toElement:(NSXMLElement *)elem { + NSXMLElement *dictElem = [[NSXMLElement alloc] initWithName:@"dict"]; + NSArray *orderedKeys = [node orderedKeySet]; + for (NSString *key in orderedKeys) { + NSXMLElement *keyElem = [[NSXMLElement alloc] initWithName:@"key"]; + [keyElem setStringValue:key]; + [dictElem addChild:keyElem]; + [keyElem release]; + [self writePListNode:[node nodeForKey:key] toElement:dictElem]; + } + [elem addChild:dictElem]; + [dictElem release]; +} +- (void)writeInteger:(IntegerNode *)node toElement:(NSXMLElement *)elem { + NSXMLElement *integerElem = [[NSXMLElement alloc] initWithName:@"integer"]; + [integerElem setStringValue:[NSString stringWithFormat:@"%qi", [node longlongValue]]]; + [elem addChild:integerElem]; + [integerElem release]; +} +- (void)writeReal:(RealNode *)node toElement:(NSXMLElement *)elem { + NSXMLElement *realElem = [[NSXMLElement alloc] initWithName:@"real"]; + [realElem setStringValue:[NSString stringWithFormat:@"%f", [node doubleValue]]]; + [elem addChild:realElem]; + [realElem release]; +} +- (void)writeString:(StringNode *)node toElement:(NSXMLElement *)elem { + NSXMLElement *stringElem = [[NSXMLElement alloc] initWithName:@"string"]; + NSString *value = (NSString *)CFXMLCreateStringByEscapingEntities(kCFAllocatorDefault, (CFStringRef)[node stringValue], NULL); + [stringElem setStringValue:value]; + [value release]; + [elem addChild:stringElem]; + [stringElem release]; +} +- (void)writePListNode:(id )node toElement:(NSXMLElement *)elem { + int type = [node type]; + switch (type) { + case PLN_ARRAY: + [self writeArray:(ArrayNode *)node toElement:elem]; + break; + case PLN_BOOLEAN: + [self writeBoolean:(BooleanNode *)node toElement:elem]; + break; + case PLN_DICT: + [self writeDict:(DictNode *)node toElement:elem]; + break; + case PLN_INTEGER: + [self writeInteger:(IntegerNode *)node toElement:elem]; + break; + case PLN_REAL: + [self writeReal:(RealNode *)node toElement:elem]; + break; + case PLN_STRING: + [self writeString:(StringNode *)node toElement:elem]; + break; + default: + @throw [NSException exceptionWithName:@"InvalidPListNodeTypeException" reason:[NSString stringWithFormat:@"invalid type %d", type] userInfo:nil]; // Programming error. + } +} +@end diff --git a/s3/HTTPConnection_S3.h b/s3/HTTPConnection_S3.h new file mode 100644 index 0000000..e37023a --- /dev/null +++ b/s3/HTTPConnection_S3.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "HTTPConnection.h" +@class S3AuthorizationProvider; + +@interface HTTPConnection (S3) +- (void)setAuthorizationRequestHeaderUsingProvider:(S3AuthorizationProvider *)sap s3BucketName:(NSString *)s3BucketName; + +@end diff --git a/s3/HTTPConnection_S3.m b/s3/HTTPConnection_S3.m new file mode 100644 index 0000000..34d78e1 --- /dev/null +++ b/s3/HTTPConnection_S3.m @@ -0,0 +1,45 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "HTTPConnection_S3.h" +#import "S3AuthorizationParameters.h" +#import "S3AuthorizationProvider.h" +#import "HTTPRequest.h" + +@implementation HTTPConnection (S3) +- (void)setAuthorizationRequestHeaderUsingProvider:(S3AuthorizationProvider *)sap s3BucketName:(NSString *)s3BucketName { + S3AuthorizationParameters *params = [[S3AuthorizationParameters alloc] initWithHTTPRequest:request s3BucketName:s3BucketName]; + [request setHeader:[sap authorizationForParameters:params] forKey:@"Authorization"]; + [params release]; +} + +@end diff --git a/s3/NSError_S3.h b/s3/NSError_S3.h new file mode 100644 index 0000000..f059cf1 --- /dev/null +++ b/s3/NSError_S3.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + +#define S3_ERROR_DOMAIN @"S3" + + +@interface NSError (S3) ++ (NSError *)errorFromAmazonXMLData:(NSData *)data statusCode:(int)statusCode; +@end diff --git a/s3/NSError_S3.m b/s3/NSError_S3.m new file mode 100644 index 0000000..331c3c9 --- /dev/null +++ b/s3/NSError_S3.m @@ -0,0 +1,59 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "NSError_S3.h" + + +@implementation NSError (S3) ++ (NSError *)errorFromAmazonXMLData:(NSData *)data statusCode:(int)statusCode { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSString *message = nil; + NSError *tmpError; + NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:data options:0 error:&tmpError] autorelease]; + if (xmlDoc != nil) { + HSLogDebug(@"amazon error XML: %@", [xmlDoc description]); + NSArray *messages = [xmlDoc nodesForXPath:@"//Error/Message" error:&tmpError]; + if (messages && [messages count] > 0) { + message = [NSString stringWithFormat:@"Amazon S3 error: %@", [[messages objectAtIndex:0] stringValue]]; + } + } else { + HSLogError(@"unable to parse S3 error XML: %@; xml=%@", [tmpError localizedDescription], [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); + } + if (message == nil) { + message = [NSString stringWithFormat:@"Amazon S3 error %d", statusCode]; + } + NSError *error = [NSError errorWithDomain:S3_ERROR_DOMAIN code:statusCode userInfo:[NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey, nil]]; + [error retain]; + [pool drain]; + return [error autorelease]; +} +@end diff --git a/s3/PathReceiver.h b/s3/PathReceiver.h new file mode 100644 index 0000000..2b61106 --- /dev/null +++ b/s3/PathReceiver.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "S3Receiver.h" + +@interface PathReceiver : NSObject { + NSMutableArray *paths; +} +- (NSArray *)paths; +@end diff --git a/s3/PathReceiver.m b/s3/PathReceiver.m new file mode 100644 index 0000000..b3a03a8 --- /dev/null +++ b/s3/PathReceiver.m @@ -0,0 +1,54 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "PathReceiver.h" + + +@implementation PathReceiver +- (id)init { + if (self = [super init]) { + paths = [[NSMutableArray alloc] init]; + } + return self; +} +- (void)dealloc { + [paths release]; + [super dealloc]; +} +- (NSArray *)paths { + return paths; +} +- (BOOL)receiveS3ObjectMetadata:(S3ObjectMetadata *)metadata error:(NSError **)error { + [paths addObject:[metadata path]]; + return YES; +} +@end diff --git a/s3/S3AuthorizationParameters.h b/s3/S3AuthorizationParameters.h new file mode 100644 index 0000000..e5c405b --- /dev/null +++ b/s3/S3AuthorizationParameters.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class HTTPRequest; + +@interface S3AuthorizationParameters : NSObject { + NSString *bucketName; + NSString *httpVerb; + NSString *contentType; + NSString *pathInfo; + NSString *subResource; + NSString *queryString; + NSString *date; + NSArray *xamzHeaders; +} +- (id)initWithHTTPRequest:(HTTPRequest *)req s3BucketName:(NSString *)theS3BucketName; +@property(readonly,copy) NSString *bucketName; +@property(readonly,copy) NSString *httpVerb; +@property(readonly,copy) NSString *contentType; +@property(readonly,copy) NSString *pathInfo; +@property(readonly,copy) NSString *subResource; +@property(readonly,copy) NSString *queryString; +@property(readonly,copy) NSString *date; +@property(readonly,retain) NSArray *xamzHeaders; + +@end diff --git a/s3/S3AuthorizationParameters.m b/s3/S3AuthorizationParameters.m new file mode 100644 index 0000000..02a8ad0 --- /dev/null +++ b/s3/S3AuthorizationParameters.m @@ -0,0 +1,81 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "S3AuthorizationParameters.h" +#import "HTTPRequest.h" + +@implementation S3AuthorizationParameters +@synthesize bucketName, httpVerb, contentType, pathInfo, subResource, queryString, date, xamzHeaders; + +- (id)initWithHTTPRequest:(HTTPRequest *)req s3BucketName:(NSString *)theS3BucketName { + if (self = [super init]) { + NSMutableArray *theXAmzHeaders = [[NSMutableArray alloc ]init]; + bucketName = [theS3BucketName copy]; + httpVerb = [[req method] retain]; + pathInfo = [[req pathInfo] retain]; + contentType = [[req headerForKey:@"Content-Type"] retain]; + if (!contentType) { + contentType = [[NSString alloc] initWithString:@""]; + } + date = [[req headerForKey:@"Date"] retain]; + queryString = [[req queryString] retain]; + if (queryString != nil + && ([queryString isEqualToString:@"?acl"] + || [queryString isEqualToString:@"?logging"] + || [queryString isEqualToString:@"?torrent"] + || [queryString isEqualToString:@"?location"])) { + subResource = queryString; + queryString = [[NSString alloc] initWithString:@""]; + } + for (NSString *headerName in [req allHeaderKeys]) { + NSString *lowerCaseHeader = [headerName lowercaseString]; + if ([lowerCaseHeader hasPrefix:@"x-amz-"]) { + [theXAmzHeaders addObject:[NSString stringWithFormat:@"%@:%@", headerName, [req headerForKey:headerName]]]; + } + } + [theXAmzHeaders sortUsingSelector:@selector(compare:)]; + xamzHeaders = theXAmzHeaders; + } + return self; +} +- (void)dealloc { + [bucketName release]; + [httpVerb release]; + [contentType release]; + [pathInfo release]; + [subResource release]; + [queryString release]; + [date release]; + [xamzHeaders release]; + [super dealloc]; +} +@end diff --git a/s3/S3AuthorizationProvider.h b/s3/S3AuthorizationProvider.h new file mode 100644 index 0000000..a6c9dc3 --- /dev/null +++ b/s3/S3AuthorizationProvider.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class S3AuthorizationParameters; + +@interface S3AuthorizationProvider : NSObject { + NSString *accessKey; + NSString *secretKey; +} +- (NSString *)accessKey; +- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret; +- (NSString *)authorizationForParameters:(S3AuthorizationParameters *)params; + +@end diff --git a/s3/S3AuthorizationProvider.m b/s3/S3AuthorizationProvider.m new file mode 100644 index 0000000..67c5373 --- /dev/null +++ b/s3/S3AuthorizationProvider.m @@ -0,0 +1,74 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "S3Signature.h" +#import "S3AuthorizationProvider.h" + + +/* + * WARNING: + * This class *must* be reentrant! + */ + +@implementation S3AuthorizationProvider +- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret { + if (self = [super init]) { + NSAssert(access != nil, @"access key can't be nil"); + NSAssert(secret != nil, @"secret key can't be nil"); + accessKey = [access copy]; + secretKey = [secret copy]; + } + return self; +} +- (void)dealloc { + [accessKey release]; + [secretKey release]; + [super dealloc]; +} +- (NSString *)accessKey { + return accessKey; +} +- (NSString *)authorizationForParameters:(S3AuthorizationParameters *)params { + NSMutableString *buf = [[[NSMutableString alloc] init] autorelease]; + [buf appendString:@"AWS "]; + [buf appendString:accessKey]; + [buf appendString:@":"]; + [buf appendString:[S3Signature signatureWithSecretKey:secretKey s3AuthorizationParameters:params]]; + NSString *ret = [NSString stringWithString:buf]; + if ([ret hasSuffix:@"\n"]) { + NSUInteger length = [ret length]; + ret = [ret substringToIndex:(length - 1)]; + } + return ret; +} + +@end diff --git a/s3/S3Lister.h b/s3/S3Lister.h new file mode 100644 index 0000000..656c405 --- /dev/null +++ b/s3/S3Lister.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "S3Receiver.h" +@class RFC2616DateFormatter; +@class S3AuthorizationProvider; + +@interface S3Lister : NSObject { + RFC2616DateFormatter *dateFormatter; + S3AuthorizationProvider *sap; + BOOL useSSL; + BOOL retryOnNetworkError; + int maxRequested; + int received; + BOOL isTruncated; + NSString *prefix; + id receiver; + NSString *marker; +} +- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)isUseSSL retryOnNetworkError:(BOOL)retry max:(int)theMax prefix:(NSString *)thePrefix receiver:(id)theReceiver; +- (BOOL)listObjects:(NSError **)error; +@end diff --git a/s3/S3Lister.m b/s3/S3Lister.m new file mode 100644 index 0000000..ba0efe4 --- /dev/null +++ b/s3/S3Lister.m @@ -0,0 +1,177 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "RFC2616DateFormatter.h" +#import "NSError_S3.h" +#import "S3AuthorizationProvider.h" +#import "S3AuthorizationParameters.h" +#import "S3Lister.h" +#import "SetNSError.h" +#import "HTTPRequest.h" +#import "HTTPResponse.h" +#import "HTTP.h" +#import "S3Service.h" +#import "S3Request.h" +#import "ServerBlob.h" + +@interface S3Lister (internal) +- (BOOL)getWithMax:(int)max error:(NSError **)error; +@end + +@implementation S3Lister +- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)isUseSSL retryOnNetworkError:(BOOL)retry max:(int)theMax prefix:(NSString *)thePrefix receiver:(id)theReceiver { + if (self = [super init]) { + dateFormatter = [[RFC2616DateFormatter alloc] init]; + sap = [theSAP retain]; + useSSL = isUseSSL; + retryOnNetworkError = retry; + maxRequested = theMax; + received = 0; + isTruncated = YES; + prefix = [thePrefix copy]; + receiver = [theReceiver retain]; + marker = nil; + } + return self; +} +- (void)dealloc { + [dateFormatter release]; + [sap release]; + [prefix release]; + [receiver release]; + [marker release]; + [super dealloc]; +} +- (BOOL)listObjects:(NSError **)error { + if (error != NULL) { + *error = nil; + } + BOOL ret = YES; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + while (isTruncated) { + if (maxRequested < 0) { + if (![self getWithMax:-1 error:error]) { + ret = NO; + break; + } + } else { + if (![self getWithMax:(maxRequested - received) error:error]) { + ret = NO; + break; + } + } + } + if (!ret && error != NULL) { + [*error retain]; + } + [pool drain]; + if (!ret && error != NULL) { + [*error autorelease]; + } + return ret; +} +@end + +@implementation S3Lister (internal) +- (BOOL)getWithMax:(int)max error:(NSError **)error { + if (![prefix hasPrefix:@"/"]) { + SETNSERROR([S3Service errorDomain], -1, @"path must start with /"); + return NO; + } + NSString *strippedPrefix = [prefix substringFromIndex:1]; + NSRange range = [strippedPrefix rangeOfString:@"/"]; + if (range.location == NSNotFound) { + SETNSERROR([S3Service errorDomain], -1, @"path must contain S3 bucket name plus path"); + return NO; + } + NSString *s3BucketName = [strippedPrefix substringToIndex:range.location]; + NSString *pathPrefix = [strippedPrefix substringFromIndex:range.location]; + NSMutableString *queryString = [NSMutableString stringWithFormat:@"?prefix=%@", [[pathPrefix substringFromIndex:1] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + if (maxRequested > 0) { + [queryString appendString:[NSString stringWithFormat:@"&max-keys=%d", maxRequested]]; + } + if (marker != nil) { + NSAssert([marker hasPrefix:s3BucketName], @"marker must start with S3 bucket name"); + NSString *suffix = [marker substringFromIndex:([s3BucketName length] + 1)]; + [queryString appendString:[NSString stringWithFormat:@"&marker=%@", [suffix stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; + } + + S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:[NSString stringWithFormat:@"/%@/", s3BucketName] queryString:queryString authorizationProvider:sap useSSL:useSSL retryOnNetworkError:retryOnNetworkError]; + ServerBlob *sb = [s3r newServerBlob:error]; + [s3r release]; + if (sb == nil) { + return NO; + } + NSData *data = [sb slurp:error]; + [sb release]; + if (data == nil) { + return NO; + } + NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:data options:0 error:error] autorelease]; + if (!xmlDoc) { + return NO; + } + NSString *lastPath = nil; + NSXMLElement *rootElement = [xmlDoc rootElement]; + NSArray *isTruncatedNodes = [rootElement nodesForXPath:@"//ListBucketResult/IsTruncated" error:error]; + if (!isTruncatedNodes || [isTruncatedNodes count] == 0) { + return NO; + } + isTruncated = [[[isTruncatedNodes objectAtIndex:0] stringValue] isEqualToString:@"true"]; + NSArray *objects = [rootElement nodesForXPath:@"//ListBucketResult/Contents" error:error]; + if (!objects) { + return NO; + } + for (NSXMLNode *objectNode in objects) { + S3ObjectMetadata *md = [[S3ObjectMetadata alloc] initWithS3BucketName:s3BucketName node:objectNode error:error]; + if (!md) { + return NO; + } + if (![receiver receiveS3ObjectMetadata:md error:error]) { + [md release]; + return NO; + } + lastPath = [[[md path] retain] autorelease]; + [md release]; + received++; + if (maxRequested > 0 && received >= maxRequested) { + isTruncated = NO; + break; + } + } + if (lastPath != nil) { + [marker release]; + marker = [[lastPath substringFromIndex:1] retain]; + } + return YES; +} +@end diff --git a/s3/S3ObjectMetadata.h b/s3/S3ObjectMetadata.h new file mode 100644 index 0000000..9f9e594 --- /dev/null +++ b/s3/S3ObjectMetadata.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface S3ObjectMetadata : NSObject { + NSString *path; + NSDate *lastModified; + long size; +} +- (id)initWithS3BucketName:(NSString *)s3BucketName node:(NSXMLNode *)node error:(NSError **)error; + +- (NSString *)path; +- (NSDate *)lastModified; +- (long)size; +@end diff --git a/s3/S3ObjectMetadata.m b/s3/S3ObjectMetadata.m new file mode 100644 index 0000000..3d46d2c --- /dev/null +++ b/s3/S3ObjectMetadata.m @@ -0,0 +1,96 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "S3ObjectMetadata.h" +#import "RFC822.h" + +@implementation S3ObjectMetadata +- (id)initWithS3BucketName:(NSString *)s3BucketName node:(NSXMLNode *)node error:(NSError **)error { + if (error != NULL) { + *error = nil; + } + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if (self = [super init]) { + NSArray *nodes = [node nodesForXPath:@"Key" error:error]; + if (!nodes) { + goto init_error; + } + NSXMLNode *keyNode = [nodes objectAtIndex:0]; + path = [[NSString alloc] initWithFormat:@"/%@/%@", s3BucketName, [keyNode stringValue]]; + nodes = [node nodesForXPath:@"LastModified" error:error]; + if (!nodes) { + goto init_error; + } + NSXMLNode *lastModifiedNode = [nodes objectAtIndex:0]; + lastModified = [[RFC822 dateFromString:[lastModifiedNode stringValue] error:error] retain]; + if (lastModified == nil) { + goto init_error; + } + nodes = [node nodesForXPath:@"Size" error:error]; + if (!nodes) { + goto init_error; + } + NSXMLNode *sizeNode = [nodes objectAtIndex:0]; + NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease]; + size = [[numberFormatter numberFromString:[sizeNode stringValue]] longValue]; + goto init_done; + init_error: + [self release]; + self = nil; + goto init_done; + } +init_done: + if (self == nil && error != NULL) { + [*error retain]; + } + [pool drain]; + if (self == nil && error != NULL) { + [*error autorelease]; + } + return self; +} +- (void)dealloc { + [path release]; + [lastModified release]; + [super dealloc]; +} +- (NSString *)path { + return path; +} +- (NSDate *)lastModified { + return lastModified; +} +- (long)size { + return size; +} + +@end diff --git a/s3/S3ObjectReceiver.h b/s3/S3ObjectReceiver.h new file mode 100644 index 0000000..5226549 --- /dev/null +++ b/s3/S3ObjectReceiver.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "S3Receiver.h" + +@interface S3ObjectReceiver : NSObject { + NSMutableArray *objects; +} +- (NSArray *)objects; +@end diff --git a/s3/S3ObjectReceiver.m b/s3/S3ObjectReceiver.m new file mode 100644 index 0000000..bb917d2 --- /dev/null +++ b/s3/S3ObjectReceiver.m @@ -0,0 +1,55 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "S3ObjectReceiver.h" + + +@implementation S3ObjectReceiver +- (id)init { + if (self = [super init]) { + objects = [[NSMutableArray alloc] init]; + } + return self; +} +- (void)dealloc { + [objects release]; + [super dealloc]; +} +- (NSArray *)objects { + return objects; +} +- (BOOL)receiveS3ObjectMetadata:(S3ObjectMetadata *)metadata error:(NSError **)error { + [objects addObject:metadata]; + return YES; +} + +@end diff --git a/s3/S3Owner.h b/s3/S3Owner.h new file mode 100644 index 0000000..3aaed98 --- /dev/null +++ b/s3/S3Owner.h @@ -0,0 +1,19 @@ +// +// S3Owner.h +// Backup +// +// Created by Stefan Reitshamer on 4/12/09. +// Copyright 2009 PhotoMinds LLC. All rights reserved. +// + +#import + + +@interface S3Owner : NSObject { + NSString *displayName; + NSString *idString; +} +- (id)initWithDisplayName:(NSString *)dn idString:(NSString *)ids; +- (NSString *)displayName; +- (NSString *)idString; +@end diff --git a/s3/S3Owner.m b/s3/S3Owner.m new file mode 100644 index 0000000..5036785 --- /dev/null +++ b/s3/S3Owner.m @@ -0,0 +1,26 @@ +// +// S3Owner.m +// Backup +// +// Created by Stefan Reitshamer on 4/12/09. +// Copyright 2009 PhotoMinds LLC. All rights reserved. +// + +#import "S3Owner.h" + + +@implementation S3Owner +- (id)initWithDisplayName:(NSString *)dn idString:(NSString *)ids { + if (self = [super init]) { + displayName = [dn copy]; + idString = [ids copy]; + } + return self; +} +- (NSString *)displayName { + return displayName; +} +- (NSString *)idString { + return idString; +} +@end diff --git a/s3/S3Receiver.h b/s3/S3Receiver.h new file mode 100644 index 0000000..9cba0a3 --- /dev/null +++ b/s3/S3Receiver.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "S3ObjectMetadata.h" + +@protocol S3Receiver +- (BOOL)receiveS3ObjectMetadata:(S3ObjectMetadata *)metadata error:(NSError **)error; +@end diff --git a/s3/S3Request.h b/s3/S3Request.h new file mode 100644 index 0000000..295dc8e --- /dev/null +++ b/s3/S3Request.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class S3AuthorizationProvider; +@class ServerBlob; + +@interface S3Request : NSObject { + NSString *method; + NSString *path; + NSString *s3BucketName; + NSString *queryString; + S3AuthorizationProvider *sap; + BOOL withSSL; + BOOL retryOnNetworkError; + uint64_t length; + NSString *virtualHost; + NSString *virtualPath; +} +- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnNetworkError:(BOOL)retry; +- (ServerBlob *)newServerBlob:(NSError **)error; +@end diff --git a/s3/S3Request.m b/s3/S3Request.m new file mode 100644 index 0000000..da2b0e7 --- /dev/null +++ b/s3/S3Request.m @@ -0,0 +1,236 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "S3Request.h" +#import "HTTP.h" +#import "HTTPConnection.h" +#import "HTTPConnection_S3.h" +#import "ServerBlob.h" +#import "Blob.h" +#import "S3Service.h" +#import "NSXMLNode_extra.h" +#import "SetNSError.h" +#import "CFStreamPair.h" +#import "RegexKitLite.h" + +#define INITIAL_RETRY_SLEEP (0.5) +#define RETRY_SLEEP_GROWTH_FACTOR (1.5) +#define MAX_RETRY_SLEEP (5.0) + +@interface S3Request (internal) +- (ServerBlob *)newServerBlobOnce:(NSError **)error; +@end + +@implementation S3Request +- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnNetworkError:(BOOL)retry { + if (self = [super init]) { + method = [theMethod copy]; + path = [thePath copy]; + NSRange s3BucketNameRange = [path rangeOfRegex:@"^/([^/]+)" capture:1]; + if (s3BucketNameRange.location != NSNotFound) { + s3BucketName = [path substringWithRange:s3BucketNameRange]; + } + queryString = [theQueryString copy]; + sap = [theSAP retain]; + withSSL = ssl; + retryOnNetworkError = retry; + } + return self; +} +- (void)dealloc { + [method release]; + [path release]; + [queryString release]; + [sap release]; + [virtualHost release]; + [virtualPath release]; + [super dealloc]; +} +- (ServerBlob *)newServerBlob:(NSError **)error { + [virtualHost release]; + virtualHost = nil; + [virtualPath release]; + virtualPath = nil; + if ([path isEqualToString:@"/"]) { + // List-bucket request. + virtualHost = [@"s3.amazonaws.com" retain]; + virtualPath = [path retain]; + } else { + NSString *pattern = @"^/([^/]+)(.+)$"; + NSRange s3BucketRange = [path rangeOfRegex:pattern capture:1]; + if (s3BucketRange.location == NSNotFound) { + SETNSERROR(@"S3ServiceErrorDomain", -1, @"invalid path-style path -- missing s3 bucket name"); + return nil; + } + NSRange pathRange = [path rangeOfRegex:pattern capture:2]; + if (pathRange.location == NSNotFound) { + SETNSERROR(@"S3ServiceErrorDomain", -1, @"invalid path-style path -- missing path"); + return nil; + } + virtualHost = [[[path substringWithRange:s3BucketRange] stringByAppendingString:@".s3.amazonaws.com"] retain]; + virtualPath = [[path substringWithRange:pathRange] retain]; + } + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSTimeInterval sleepTime = INITIAL_RETRY_SLEEP; + ServerBlob *sb = nil; + NSError *myError = nil; + for (;;) { + myError = nil; + NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init]; + sb = [self newServerBlobOnce:&myError]; + [myError retain]; + [pool2 drain]; + [myError autorelease]; + if (sb != nil) { + break; + } + BOOL needSleep = NO; + if ([[myError domain] isEqualToString:[S3Service serverErrorDomain]]) { + NSString *amazonErrorCode = [[myError userInfo] objectForKey:@"Code"]; + if ([myError code] == HTTP_INTERNAL_SERVER_ERROR) { + HSLogInfo(@"S3 returned %u; retrying", HTTP_INTERNAL_SERVER_ERROR); + needSleep = YES; + } else if ([myError code] == HTTP_BAD_REQUEST && [amazonErrorCode isEqualToString:@"RequestTimeout"]) { + HSLogInfo(@"s3 RequestTimeout; retrying"); + } else if ([myError code] == HTTP_SERVICE_NOT_AVAILABLE) { + HSLogInfo(@"S3 returned @u; retrying", HTTP_SERVICE_NOT_AVAILABLE); + needSleep = YES; + } else if ([myError code] == HTTP_MOVED_TEMPORARILY) { + [virtualHost release]; + virtualHost = [[[myError userInfo] objectForKey:@"Endpoint"] copy]; + HSLogDebug(@"S3 redirect to %@", virtualHost); + } else { + if ([myError code] != HTTP_NOT_FOUND) { + HSLogError(@"error getting %@: %@", virtualPath, [myError localizedDescription]); + } + break; + } + } else if ([[myError domain] isEqualToString:[CFStreamPair errorDomain]] && retryOnNetworkError) { + HSLogDebug(@"network error (retrying): %@", [myError localizedDescription]); + needSleep = YES; + } else { + break; + } + if (needSleep) { + [NSThread sleepForTimeInterval:sleepTime]; + sleepTime *= RETRY_SLEEP_GROWTH_FACTOR; + if (sleepTime > MAX_RETRY_SLEEP) { + sleepTime = MAX_RETRY_SLEEP; + } + } + } + [myError retain]; + [pool drain]; + [myError autorelease]; + if (sb == nil && error != NULL) { + NSAssert(myError != nil, @"myError must be set"); + *error = myError; + } + return sb; +} +@end + +@implementation S3Request (internal) +- (ServerBlob *)newServerBlobOnce:(NSError **)error { + HTTPConnection *conn = [[[HTTPConnection alloc] initWithHost:virtualHost useSSL:withSSL error:error] autorelease]; + if (conn == nil) { + return nil; + } + [conn setRequestMethod:method pathInfo:[virtualPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] queryString:queryString protocol:HTTP_1_1]; + [conn setRequestHostHeader]; + [conn setRFC822DateRequestHeader]; + [conn setRequestKeepAliveHeader]; + [conn setAuthorizationRequestHeaderUsingProvider:sap s3BucketName:s3BucketName]; + BOOL execRet = [conn executeRequest:error]; + if (!execRet) { + return nil; + } + ServerBlob *ret = nil; + int code = [conn responseCode]; + if (code >= 200 && code <= 299) { + id bodyStream = [conn newResponseBodyStream:error]; + if (bodyStream == nil) { + return nil; + } + ret = [[ServerBlob alloc] initWithInputStream:bodyStream mimeType:[conn responseMimeType] downloadName:[conn responseDownloadName]]; + [bodyStream release]; + } else { + if (code >= 400 && code != HTTP_NOT_FOUND) { + HSLogDebug(@"S3 HTTP response code was %d; requesting close on connection", code); + [conn setCloseRequested]; + } + NSData *response = [conn slurpResponseBody:error]; + if (response == nil) { + return nil; + } + NSXMLDocument *xmlDoc = [[NSXMLDocument alloc] initWithData:response options:0 error:error]; + HSLogTrace(@"%@", [xmlDoc description]); + if (xmlDoc == nil) { + return nil; + } + NSXMLElement *rootElement = [xmlDoc rootElement]; + NSArray *errorNodes = [rootElement nodesForXPath:@"//Error" error:error]; + if (errorNodes == nil) { + [xmlDoc release]; + return nil; + } else if ([errorNodes count] == 0) { + HSLogWarn(@"missing Error node in S3 XML response"); + SETNSERROR([S3Service errorDomain], code, @"S3 error"); + [xmlDoc release]; + return nil; + } else { + if ([errorNodes count] > 1) { + HSLogWarn(@"ignoring additional S3 errors"); + } + NSXMLNode *errorNode = [errorNodes objectAtIndex:0]; + NSString *errorCode = [[errorNode childNodeNamed:@"Code"] stringValue]; + NSString *errorMessage = [[errorNode childNodeNamed:@"Message"] stringValue]; + if (code == HTTP_NOT_FOUND) { + errorMessage = [NSString stringWithFormat:@"%@ not found", virtualPath]; + } + NSString *endpoint = (code == HTTP_MOVED_TEMPORARILY) ? [[errorNode childNodeNamed:@"Endpoint"] stringValue] : nil; + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + (errorCode != nil ? errorCode : @""), @"Code", + (errorMessage != nil ? errorMessage : @""), @"Message", + (errorMessage != nil ? errorMessage : @""), NSLocalizedDescriptionKey, + (endpoint != nil ? endpoint : @""), @"Endpoint", + nil]; + NSError *myError = [NSError errorWithDomain:[S3Service serverErrorDomain] code:code userInfo:userInfo]; + if (error != NULL) { + *error = myError; + } + } + [xmlDoc release]; + } + return ret; +} +@end diff --git a/s3/S3Service.h b/s3/S3Service.h new file mode 100644 index 0000000..3020ab3 --- /dev/null +++ b/s3/S3Service.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "S3Receiver.h" +@class Blob; +@class S3AuthorizationProvider; +@class ServerBlob; + +enum { + BUCKET_REGION_US_STANDARD = 0, + BUCKET_REGION_US_WEST = 1, + BUCKET_REGION_EU = 2 +}; + +@interface S3Service : NSObject { + S3AuthorizationProvider *sap; + BOOL useSSL; + BOOL retryOnNetworkError; +} ++ (NSString *)errorDomain; ++ (NSString *)serverErrorDomain; ++ (NSString *)displayNameForBucketRegion:(int)region; +- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)useSSL retryOnNetworkError:(BOOL)retry; +- (NSArray *)s3BucketNames:(NSError **)error; +- (BOOL)s3BucketExists:(NSString *)s3BucketName; + +- (NSArray *)pathsWithPrefix:(NSString *)prefix error:(NSError **)error; +- (NSArray *)objectsWithPrefix:(NSString *)prefix error:(NSError **)error; +- (BOOL)listObjectsWithPrefix:(NSString *)prefix receiver:(id )receiver error:(NSError **)error; +- (BOOL)listObjectsWithMax:(int)maxResults prefix:(NSString *)prefix receiver:(id )receiver error:(NSError **)error; +- (BOOL)containsBlobAtPath:(NSString *)path; + +- (NSData *)dataAtPath:(NSString *)path error:(NSError **)error; +- (ServerBlob *)newServerBlobAtPath:(NSString *)path error:(NSError **)error; + + +- (NSArray *)commonPrefixesForPathPrefix:(NSString *)prefix delimiter:(NSString *)delimiter error:(NSError **)error; + +@end diff --git a/s3/S3Service.m b/s3/S3Service.m new file mode 100644 index 0000000..ab4f827 --- /dev/null +++ b/s3/S3Service.m @@ -0,0 +1,238 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "Blob.h" +#import "RegexKitLite.h" +#import "NSError_S3.h" +#import "S3Lister.h" +#import "S3AuthorizationParameters.h" +#import "S3AuthorizationProvider.h" +#import "S3Service.h" +#import "PathReceiver.h" +#import "SetNSError.h" +#import "HTTPResponse.h" +#import "HTTPRequest.h" +#import "HTTP.h" +#import "S3ObjectReceiver.h" +#import "ServerBlob.h" +#import "NSErrorCodes.h" +#import "S3Request.h" + +/* + * WARNING: + * This class *must* be reentrant! + */ + +@interface S3Service (internal) +- (NSXMLDocument *)listBuckets:(NSError **)error; +@end + +@implementation S3Service ++ (NSString *)errorDomain { + return @"S3ServiceErrorDomain"; +} ++ (NSString *)serverErrorDomain { + return @"S3ServiceServerErrorDomain"; +} ++ (NSString *)displayNameForBucketRegion:(int)region { + switch (region) { + case BUCKET_REGION_US_STANDARD: + return @"US Standard"; + case BUCKET_REGION_US_WEST: + return @"US West"; + case BUCKET_REGION_EU: + return @"EU"; + } + NSAssert(NO, @"invalid S3 bucket region"); + return nil; +} +- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)isUseSSL retryOnNetworkError:(BOOL)retry { + if (self = [super init]) { + sap = [theSAP retain]; + useSSL = isUseSSL; + retryOnNetworkError = retry; + } + return self; +} +- (void)dealloc { + [sap release]; + [super dealloc]; +} +- (NSArray *)s3BucketNames:(NSError **)error { + NSXMLDocument *doc = [self listBuckets:error]; + if (!doc) { + return nil; + } + NSXMLElement *rootElem = [doc rootElement]; + NSArray *nameNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Buckets/Bucket/Name" error:error]; + if (!nameNodes) { + return nil; + } + NSMutableArray *bucketNames = [[[NSMutableArray alloc] init] autorelease]; + for (NSXMLNode *nameNode in nameNodes) { + [bucketNames addObject:[nameNode stringValue]]; + } + return bucketNames; +} +- (BOOL)s3BucketExists:(NSString *)s3BucketName { + NSError *error = nil; + NSArray *s3BucketNames = [self s3BucketNames:&error]; + if (!s3BucketNames) { + HSLogDebug(@"error getting S3 bucket names: %@", [error localizedDescription]); + return NO; + } + return [s3BucketNames containsObject:s3BucketName]; +} +- (NSArray *)pathsWithPrefix:(NSString *)prefix error:(NSError **)error { + NSArray *array = nil; + PathReceiver *rec = [[[PathReceiver alloc] init] autorelease]; + if (rec && [self listObjectsWithPrefix:prefix receiver:rec error:error]) { + array = [rec paths]; + } + return array; +} +- (NSArray *)objectsWithPrefix:(NSString *)prefix error:(NSError **)error { + S3ObjectReceiver *receiver = [[[S3ObjectReceiver alloc] init] autorelease]; + if (![self listObjectsWithPrefix:prefix receiver:receiver error:error]) { + return NO; + } + return [receiver objects]; +} +- (BOOL)listObjectsWithPrefix:(NSString *)prefix receiver:(id )receiver error:(NSError **)error { + return [self listObjectsWithMax:-1 prefix:prefix receiver:receiver error:error]; +} +- (BOOL)listObjectsWithMax:(int)maxResults prefix:(NSString *)prefix receiver:(id )receiver error:(NSError **)error { + S3Lister *lister = [[[S3Lister alloc] initWithS3AuthorizationProvider:sap useSSL:useSSL retryOnNetworkError:retryOnNetworkError max:maxResults prefix:prefix receiver:receiver] autorelease]; + return lister && [lister listObjects:error]; +} +- (BOOL)containsBlobAtPath:(NSString *)path { + NSError *error = nil; + PathReceiver *rec = [[PathReceiver alloc] init]; + S3Lister *lister = [[S3Lister alloc] initWithS3AuthorizationProvider:sap useSSL:useSSL retryOnNetworkError:retryOnNetworkError max:-1 prefix:path receiver:rec]; + BOOL ret = [lister listObjects:&error]; + if (!ret) { + HSLogError(@"listObjects(%@): %@", path, [error localizedDescription]); + } else { + ret = [[rec paths] containsObject:path]; + HSLogDebug(@"S3 path %@ %@", path, (ret ? @"exists" : @"does not exist")); + } + [lister release]; + [rec release]; + return ret; +} +- (NSData *)dataAtPath:(NSString *)path error:(NSError **)error { + ServerBlob *sb = [self newServerBlobAtPath:path error:error]; + if (sb == nil) { + return nil; + } + NSData *data = [sb slurp:error]; + [sb release]; + return data; +} +- (ServerBlob *)newServerBlobAtPath:(NSString *)path error:(NSError **)error { + HSLogDebug(@"getting %@", path); + S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:path queryString:nil authorizationProvider:sap useSSL:useSSL retryOnNetworkError:retryOnNetworkError]; + ServerBlob *sb = [s3r newServerBlob:error]; + [s3r release]; + return sb; +} +- (NSArray *)commonPrefixesForPathPrefix:(NSString *)prefix delimiter:(NSString *)delimiter error:(NSError **)error { + if (![prefix hasPrefix:@"/"]) { + HSLogError(@"invalid prefix %@", prefix); + SETNSERROR([S3Service errorDomain], -1, @"path must begin with /"); + return nil; + } + NSRange searchRange = NSMakeRange(1, [prefix length] - 1); + NSRange nextSlashRange = [prefix rangeOfString:@"/" options:0 range:searchRange]; + if (nextSlashRange.location == NSNotFound) { + SETNSERROR([S3Service errorDomain], -1, @"path must be of the format //path"); + return nil; + } + NSString *s3BucketName = [prefix substringWithRange:NSMakeRange(1, nextSlashRange.location - 1)]; + NSString *subPath = [prefix substringFromIndex:nextSlashRange.location + 1]; + NSString *urlPath = [NSString stringWithFormat:@"/%@/", s3BucketName]; + NSString *queryString = [NSString stringWithFormat:@"?prefix=%@&delimiter=%@", + [subPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + delimiter]; + + S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:urlPath queryString:queryString authorizationProvider:sap useSSL:useSSL retryOnNetworkError:retryOnNetworkError]; + ServerBlob *sb = [s3r newServerBlob:error]; + [s3r release]; + if (sb == nil) { + return nil; + } + NSData *output = [sb slurp:error]; + [sb release]; + if (output == nil) { + return nil; + } + NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:output options:0 error:error] autorelease]; + if (xmlDoc == nil) { + SETNSERROR([S3Service errorDomain], -1, @"failed to parse XML"); + return nil; + } + NSXMLElement *rootElement = [xmlDoc rootElement]; + NSArray *objects = [rootElement nodesForXPath:@"//ListBucketResult/CommonPrefixes/Prefix" error:error]; + if (objects == nil) { + return nil; + } + NSMutableArray *commonPrefixes = [NSMutableArray array]; + if ([objects count] > 0) { + NSUInteger subPathLen = [subPath length]; + for (NSXMLNode *objectNode in objects) { + NSString *prefix = [objectNode stringValue]; + NSUInteger prefixLen = [prefix length]; + NSRange range = NSMakeRange(subPathLen, prefixLen - subPathLen - 1); + NSString *prefixSubstring = [prefix substringWithRange:range]; + [commonPrefixes addObject:prefixSubstring]; + } + } + return commonPrefixes; +} +@end + +@implementation S3Service (internal) +- (NSXMLDocument *)listBuckets:(NSError **)error { + S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:@"/" queryString:nil authorizationProvider:sap useSSL:useSSL retryOnNetworkError:retryOnNetworkError]; + ServerBlob *sb = [s3r newServerBlob:error]; + [s3r release]; + if (sb == nil) { + return nil; + } + NSData *data = [sb slurp:error]; + [sb release]; + if (data == nil) { + return nil; + } + return [[[NSXMLDocument alloc] initWithData:data options:0 error:error] autorelease]; +} +@end diff --git a/s3/S3Signature.h b/s3/S3Signature.h new file mode 100644 index 0000000..445b4ca --- /dev/null +++ b/s3/S3Signature.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +@class S3AuthorizationParameters; + +@interface S3Signature : NSObject { + +} ++ (NSString *)signatureWithSecretKey:(NSString *)secretKey s3AuthorizationParameters:(S3AuthorizationParameters *)params; +@end diff --git a/s3/S3Signature.m b/s3/S3Signature.m new file mode 100644 index 0000000..c78035c --- /dev/null +++ b/s3/S3Signature.m @@ -0,0 +1,85 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#import "NSData-Base64Extensions.h" +#import "S3AuthorizationParameters.h" +#import "S3Signature.h" + + +@implementation S3Signature ++ (NSString *)signatureWithSecretKey:(NSString *)secretKey s3AuthorizationParameters:(S3AuthorizationParameters *)params { + NSMutableString *buf = [[NSMutableString alloc] init]; + [buf appendString:[params httpVerb]]; + [buf appendString:@"\n"]; + // No Content-MD5 + [buf appendString:@"\n"]; + [buf appendString:[params contentType]]; + [buf appendString:@"\n"]; + [buf appendString:[params date]]; + [buf appendString:@"\n"]; + for (NSString *xamzHeader in [params xamzHeaders]) { + [buf appendString:xamzHeader]; + [buf appendString:@"\n"]; + } + if ([[params bucketName] length] > 0) { + [buf appendString:@"/"]; + [buf appendString:[params bucketName]]; + } + [buf appendString:[params pathInfo]]; + if ([[params subResource] length] > 0) { + [buf appendString:[params subResource]]; + } +#if 0 + { + HSLogDebug(@"string to sign: <%@>", buf); + const char *stringToSignBytes = [buf UTF8String]; + int stringToSignLen = strlen(stringToSignBytes); + NSMutableString *displayBytes = [[[NSMutableString alloc] init] autorelease]; + for (int i = 0; i < stringToSignLen; i++) { + [displayBytes appendString:[NSString stringWithFormat:@"%02x ", stringToSignBytes[i]]]; + } + HSLogDebug(@"string to sign bytes: <%@>", displayBytes); + } +#endif + NSData *clearTextData = [buf dataUsingEncoding:NSUTF8StringEncoding]; + NSData *secretKeyData = [secretKey dataUsingEncoding:NSUTF8StringEncoding]; + [buf release]; + unsigned char digest[CC_SHA1_DIGEST_LENGTH]; + CCHmac(kCCHmacAlgSHA1, [secretKeyData bytes], [secretKeyData length], [clearTextData bytes], [clearTextData length], digest); + NSData *hmacSHA1 = [[NSData alloc] initWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; + NSString *base64 = [hmacSHA1 encodeBase64]; + [hmacSHA1 release]; + return base64; +} + +@end diff --git a/shared/BinarySHA1.h b/shared/BinarySHA1.h new file mode 100644 index 0000000..d5a63c8 --- /dev/null +++ b/shared/BinarySHA1.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface BinarySHA1 : NSObject { +} ++ (NSComparisonResult)compare:(const void *)a to:(const void *)b; +@end diff --git a/shared/BinarySHA1.m b/shared/BinarySHA1.m new file mode 100644 index 0000000..669a258 --- /dev/null +++ b/shared/BinarySHA1.m @@ -0,0 +1,50 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "BinarySHA1.h" + + +@implementation BinarySHA1 ++ (NSComparisonResult)compare:(const void *)a to:(const void *)b { + unsigned char *left = (unsigned char *)a; + unsigned char *right = (unsigned char *)b; + for (int i = 0; i < 20; i++) { + if (left[i] < right[i]) { + return NSOrderedAscending; + } + if (left[i] > right[i]) { + return NSOrderedDescending; + } + } + return NSOrderedSame; +} +@end diff --git a/shared/Blob.h b/shared/Blob.h new file mode 100644 index 0000000..d3bd296 --- /dev/null +++ b/shared/Blob.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStreamFactory.h" + +@interface Blob : NSObject { + NSString *mimeType; + NSString *downloadName; + BOOL gzipped; + id inputStreamFactory; + BOOL entireSource; + unsigned long long sourceOffset; + unsigned long long sourceLength; +} +- (id)initWithInputStreamFactory:(id )theFactory mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; +- (id)initWithInputStreamFactory:(id )theFactory sourceOffset:(unsigned long long)theOffset sourceLength:(unsigned long long)theLength mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; +- (id)initWithData:(NSData *)theData mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; +- (id)initWithGzippedData:(NSData *)theData mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; +- (NSString *)mimeType; +- (NSString *)downloadName; +- (BOOL)gzipped; +- (id )newInputStream:(id)sender; +- (NSData *)slurp:(NSError **)error; +- (NSData *)slurp:(id)sender error:(NSError **)error; +@end diff --git a/shared/Blob.m b/shared/Blob.m new file mode 100644 index 0000000..63d34ae --- /dev/null +++ b/shared/Blob.m @@ -0,0 +1,112 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "Blob.h" +#import "DataInputStreamFactory.h" + +@implementation Blob +- (id)initWithInputStreamFactory:(id )theFactory mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { + if (self = [super init]) { + mimeType = [theMimeType copy]; + downloadName = [theDownloadName copy]; + gzipped = NO; + inputStreamFactory = [theFactory retain]; + entireSource = YES; + } + return self; +} +- (id)initWithInputStreamFactory:(id )theFactory sourceOffset:(unsigned long long)theOffset sourceLength:(unsigned long long)theLength mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { + if (self = [super init]) { + mimeType = [theMimeType copy]; + downloadName = [theDownloadName copy]; + gzipped = NO; + inputStreamFactory = [theFactory retain]; + entireSource = NO; + sourceOffset = theOffset; + sourceLength = theLength; + } + return self; +} +- (id)initWithData:(NSData *)theData mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { + if (self = [super init]) { + mimeType = [theMimeType copy]; + downloadName = [theDownloadName copy]; + gzipped = NO; + inputStreamFactory = [[DataInputStreamFactory alloc] initWithData:theData]; + entireSource = YES; + } + return self; +} +- (id)initWithGzippedData:(NSData *)theData mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { + if (self = [super init]) { + mimeType = [theMimeType copy]; + downloadName = [theDownloadName copy]; + gzipped = YES; + inputStreamFactory = [[DataInputStreamFactory alloc] initWithData:theData]; + entireSource = YES; + } + return self; +} +- (void)dealloc { + [mimeType release]; + [downloadName release]; + [inputStreamFactory release]; + [super dealloc]; +} +- (NSString *)mimeType { + return mimeType; +} +- (NSString *)downloadName { + return downloadName; +} +- (BOOL)gzipped { + return gzipped; +} +- (id )newInputStream:(id)sender { + id is = nil; + if (entireSource) { + is = [inputStreamFactory newInputStream:sender]; + } else { + is = [inputStreamFactory newInputStream:sender sourceOffset:sourceOffset sourceLength:sourceLength]; + } + return is; +} +- (NSData *)slurp:(NSError **)error { + return [self slurp:self error:error]; +} +- (NSData *)slurp:(id)sender error:(NSError **)error { + id is = [self newInputStream:sender]; + NSData *data = [is slurp:error]; + [is release]; + return data; +} +@end diff --git a/shared/BlobACL.h b/shared/BlobACL.h new file mode 100644 index 0000000..c4224ea --- /dev/null +++ b/shared/BlobACL.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +enum { + PRIVATE = 1, + PUBLIC_READ = 2, + PUBLIC_READ_WRITE = 6, + AUTHENTICATED_READ = 8 +}; + +#import + +@interface BlobACL : NSObject { + +} ++ (NSString *)displayNameForBlobACL:(int)blobACL; ++ (NSString *)s3NameForBlobACL:(int)blobACL; ++ (int)blobACLForS3Name:(NSString *)s3ACLName; +@end diff --git a/shared/BlobACL.m b/shared/BlobACL.m new file mode 100644 index 0000000..f6c1437 --- /dev/null +++ b/shared/BlobACL.m @@ -0,0 +1,79 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "BlobACL.h" + +@implementation BlobACL ++ (NSString *)s3NameForBlobACL:(int)blobACL { + switch(blobACL) { + case PUBLIC_READ: + return @"public-read"; + case PUBLIC_READ_WRITE: + return @"public-read-write"; + case AUTHENTICATED_READ: + return @"authenticated-read"; + default: + return @"private"; + } +} ++ (NSString *)displayNameForBlobACL:(int)blobACL { + switch(blobACL) { + case PUBLIC_READ: + return @"Public (unlisted)"; + case PUBLIC_READ_WRITE: + return @"Public read/write"; + case AUTHENTICATED_READ: + return @"Authenticated Read"; + default: + return @"Private"; + } +} ++ (int)blobACLForS3Name:(NSString *)s3ACLName { + if (!s3ACLName) { + return 0; + } + if ([s3ACLName caseInsensitiveCompare:@"public-read"] == NSOrderedSame) { + return PUBLIC_READ; + } + if ([s3ACLName caseInsensitiveCompare:@"public-read-write"] == NSOrderedSame) { + return PUBLIC_READ_WRITE; + } + if ([s3ACLName caseInsensitiveCompare:@"authenticated-read"] == NSOrderedSame) { + return AUTHENTICATED_READ; + } + if ([s3ACLName caseInsensitiveCompare:@"private"] == NSOrderedSame) { + return PRIVATE; + } + return 0; +} +@end + diff --git a/shared/DNS_SDErrors.h b/shared/DNS_SDErrors.h new file mode 100644 index 0000000..7d3cea3 --- /dev/null +++ b/shared/DNS_SDErrors.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface DNS_SDErrors : NSObject { + +} ++ (NSString *)descriptionForDNS_SDError:(int)code; +@end diff --git a/shared/DNS_SDErrors.m b/shared/DNS_SDErrors.m new file mode 100644 index 0000000..9b479e6 --- /dev/null +++ b/shared/DNS_SDErrors.m @@ -0,0 +1,101 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include "dns_sd.h" +#import "DNS_SDErrors.h" + + +@implementation DNS_SDErrors ++ (NSString *)descriptionForDNS_SDError:(int)code { + switch(code) { + case kDNSServiceErr_NoError: + return @"DNS service no error"; + case kDNSServiceErr_Unknown: + return @"DNS service unknown error"; + case kDNSServiceErr_NoSuchName: + return @"DNS service: no such name"; + case kDNSServiceErr_NoMemory: + return @"DNS service: no memory"; + case kDNSServiceErr_BadParam: + return @"DNS service: bad parameter"; + case kDNSServiceErr_BadReference: + return @"DNS service: bad reference"; + case kDNSServiceErr_BadState: + return @"DNS service: bad state"; + case kDNSServiceErr_BadFlags: + return @"DNS service: bad flags"; + case kDNSServiceErr_Unsupported: + return @"DNS service: unsupported"; + case kDNSServiceErr_NotInitialized: + return @"DNS service: not initialized"; + case kDNSServiceErr_AlreadyRegistered: + return @"DNS service: already registered"; + case kDNSServiceErr_NameConflict: + return @"DNS service: name conflict"; + case kDNSServiceErr_Invalid: + return @"DNS service: invalid"; + case kDNSServiceErr_Firewall: + return @"DNS service: firewall error"; + case kDNSServiceErr_Incompatible: + return @"DNS service: incompatible"; + case kDNSServiceErr_BadInterfaceIndex: + return @"DNS service: bad interface index"; + case kDNSServiceErr_Refused: + return @"DNS service: refused"; + case kDNSServiceErr_NoSuchRecord: + return @"DNS service: no such record"; + case kDNSServiceErr_NoAuth: + return @"DNS service: no auth"; + case kDNSServiceErr_NoSuchKey: + return @"DNS service: no such key"; + case kDNSServiceErr_NATTraversal: + return @"DNS service: NAT traversal error"; + case kDNSServiceErr_DoubleNAT: + return @"DNS service: double NAT error"; + case kDNSServiceErr_BadTime: + return @"DNS service: bad time"; + case kDNSServiceErr_BadSig: + return @"DNS service: bad sig"; + case kDNSServiceErr_BadKey: + return @"DNS service: bad key"; + case kDNSServiceErr_Transient: + return @"DNS service: transient"; + case kDNSServiceErr_ServiceNotRunning: + return @"DNS service not running"; + case kDNSServiceErr_NATPortMappingUnsupported: + return @"DNS service: NAT port mapping unsupported"; + case kDNSServiceErr_NATPortMappingDisabled: + return @"DNS service: NAT port mapping disabled"; + } + return @"unknown DNS service error"; +} +@end diff --git a/shared/FileACL.h b/shared/FileACL.h new file mode 100644 index 0000000..2848b99 --- /dev/null +++ b/shared/FileACL.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface FileACL : NSObject { +} ++ (BOOL)aclText:(NSString **)aclText forFile:(NSString *)path error:(NSError **)error; ++ (BOOL)writeACLText:(NSString *)aclText toFile:(NSString *)path error:(NSError **)error; +@end diff --git a/shared/FileACL.m b/shared/FileACL.m new file mode 100644 index 0000000..d6f6349 --- /dev/null +++ b/shared/FileACL.m @@ -0,0 +1,85 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#include +#include +#import "FileACL.h" +#import "SetNSError.h" + +@implementation FileACL ++ (BOOL)aclText:(NSString **)aclText forFile:(NSString *)path error:(NSError **)error { + *aclText = nil; + const char *pathChars = [path fileSystemRepresentation]; + acl_t acl = acl_get_link_np(pathChars, ACL_TYPE_EXTENDED); + if (!acl) { + if (errno != ENOENT) { + SETNSERROR(@"UnixErrorDomain", errno, @"acl_get_link_np: %s", strerror(errno)); + return NO; + } + } else { + char *aclTextChars = acl_to_text(acl, NULL); + if (!aclTextChars) { + acl_free(acl); + SETNSERROR(@"UnixErrorDomain", errno, @"acl_to_text: %s", strerror(errno)); + return NO; + } + *aclText = [NSString stringWithUTF8String:aclTextChars]; + acl_free(aclTextChars); + acl_free(acl); + } + return YES; +} ++ (BOOL)writeACLText:(NSString *)aclText toFile:(NSString *)path error:(NSError **)error { + const char *pathChars = [path fileSystemRepresentation]; + acl_t acl = acl_from_text([aclText UTF8String]); + if (!acl) { + SETNSERROR(@"UnixErrorDomain", errno, @"acl_from_text: %s", strerror(errno)); + return NO; + } + struct stat st; + if (lstat(pathChars, &st) == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"%s", strerror(errno)); + return NO; + } + int ret = 0; + if ((st.st_mode & S_IFMT) == S_IFLNK) { + ret = acl_set_link_np(pathChars, ACL_TYPE_EXTENDED, acl); + } else { + ret = acl_set_file(pathChars, ACL_TYPE_EXTENDED, acl); + } + if (ret == -1) { + SETNSERROR(@"UnixErrorDomain", errno, @"acl_set: %s", strerror(errno)); + return NO; + } + return YES; +} +@end diff --git a/shared/NSErrorCodes.h b/shared/NSErrorCodes.h new file mode 100644 index 0000000..997589f --- /dev/null +++ b/shared/NSErrorCodes.h @@ -0,0 +1,37 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#define ERROR_NOT_FOUND -2 +#define ERROR_INVALID_OBJECT_VERSION -3 +#define ERROR_EOF -4 +#define ERROR_NOT_LICENSED -5 +#define ERROR_BUCKET_CONFIGURATION_CHANGED -6 diff --git a/shared/NSString_extra.h b/shared/NSString_extra.h new file mode 100644 index 0000000..cc2ecb1 --- /dev/null +++ b/shared/NSString_extra.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + +@interface NSString (extra) ++ (NSString *)hexStringWithBytes:(unsigned char *)bytes length:(unsigned int)length; +- (NSString *)stringWithUniquePath; +- (NSData *)hexStringToData; +@end diff --git a/shared/NSString_extra.m b/shared/NSString_extra.m new file mode 100644 index 0000000..1ed1ee6 --- /dev/null +++ b/shared/NSString_extra.m @@ -0,0 +1,90 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "NSString_extra.h" +#import "RegexKitLite.h" + +static NSString *PATH_PATTERN = @"^(.+)(\\.\\w+)$"; + +static unsigned char hexCharToInt(char c1) { + if (c1 >= '0' && c1 <= '9') { + return c1 - '0'; + } + if (c1 >= 'a' && c1 <= 'f') { + return c1 - 'a' + 10; + } + if (c1 >= 'A' && c1 <= 'F') { + return c1 - 'A' + 10; + } + @throw [NSException exceptionWithName:@"Invalid hex char" reason:@"not a hex char" userInfo:nil]; +} + +@implementation NSString (extra) ++ (NSString *)hexStringWithBytes:(unsigned char *)bytes length:(unsigned int)length { + char *buf = (char *)malloc(length * 2 + 1); + for (unsigned int i = 0; i < length; i++) { + unsigned char c = bytes[i]; + snprintf(buf + i*2, 3, "%02x", (unsigned int)c); + } + NSString *ret = [[[NSString alloc] initWithBytes:buf length:length*2 encoding:NSUTF8StringEncoding] autorelease]; + free(buf); + return ret; +} +- (NSString *)stringWithUniquePath { + NSString *left = self; + NSString *right = @""; + if ([self rangeOfRegex:PATH_PATTERN].location != NSNotFound) { + left = [self substringWithRange:[self rangeOfRegex:PATH_PATTERN capture:1]]; + right = [self substringWithRange:[self rangeOfRegex:PATH_PATTERN capture:2]]; + } + NSFileManager *fm = [NSFileManager defaultManager]; + NSUInteger index = 2; + NSString *path = [NSString stringWithString:self]; + while ([fm fileExistsAtPath:path]) { + path = [NSString stringWithFormat:@"%@_%u%@", left, index++, right]; + } + return path; +} +- (NSData *)hexStringToData { + const char *ascii = [self cStringUsingEncoding:NSASCIIStringEncoding]; + size_t len = strlen(ascii) / 2; + NSMutableData *data = [NSMutableData dataWithLength:len]; + char *bytes = (char *)[data mutableBytes]; + for (size_t i = 0; i < len; i++) { + unsigned char c1 = hexCharToInt(ascii[i*2]) << 4; + unsigned char c2 = hexCharToInt(ascii[i*2 + 1]); + unsigned char tmp = c1 | c2; + bytes[i] = tmp; + } + return data; +} +@end diff --git a/shared/NSXMLNode_extra.h b/shared/NSXMLNode_extra.h new file mode 100644 index 0000000..96c40fa --- /dev/null +++ b/shared/NSXMLNode_extra.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface NSXMLNode (extra) +- (NSXMLNode *)childNodeNamed:(NSString *)name; +@end diff --git a/shared/NSXMLNode_extra.m b/shared/NSXMLNode_extra.m new file mode 100644 index 0000000..11119e8 --- /dev/null +++ b/shared/NSXMLNode_extra.m @@ -0,0 +1,52 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "NSXMLNode_extra.h" + +@implementation NSXMLNode (extra) +- (NSXMLNode *)childNodeNamed:(NSString *)name { + NSError *error = nil; + NSArray *childNodes = [self nodesForXPath:name error:&error]; + if (childNodes == nil) { + HSLogError(@"error reading nodesForXPath(%@): %@", name, [error localizedDescription]); + return nil; + } + if ([childNodes count] == 0) { + HSLogWarn(@"child node %@ not found for node %@", name, [self description]); + return nil; + } + if ([childNodes count] > 1) { + HSLogWarn(@"ignoring additional child nodes named %@ of node %@", name, [self description]); + } + return [childNodes objectAtIndex:0]; +} +@end diff --git a/shared/OSStatusDescription.h b/shared/OSStatusDescription.h new file mode 100644 index 0000000..fba5486 --- /dev/null +++ b/shared/OSStatusDescription.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface OSStatusDescription : NSObject { + +} ++ (NSString *)descriptionForMacOSStatus:(OSStatus)status; +@end diff --git a/shared/OSStatusDescription.m b/shared/OSStatusDescription.m new file mode 100644 index 0000000..84c49a1 --- /dev/null +++ b/shared/OSStatusDescription.m @@ -0,0 +1,55 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "OSStatusDescription.h" + + +@implementation OSStatusDescription ++ (NSString *)descriptionForMacOSStatus:(OSStatus)status { + NSString *msg = nil; + switch (status) { + case ioErr: + return @"I/O error"; // GetMacOSStatusCommentString() returns "I/O error (bummers)", which isn't appropriate! + case nsvErr: + return @"No such volume"; + case bdNamErr: + return @"Bad file name"; + case fnfErr: + return @"File not found"; + } + [NSString stringWithUTF8String:GetMacOSStatusCommentString(status)]; + if ([msg length] == 0) { + msg = [NSString stringWithFormat:@"error %d", status]; + } + return msg; +} +@end diff --git a/shared/RFC822.h b/shared/RFC822.h new file mode 100644 index 0000000..c5ffc5b --- /dev/null +++ b/shared/RFC822.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import + + +@interface RFC822 : NSObject { + +} ++ (NSDate *)dateFromString:(NSString *)dateString error:(NSError **)error; +@end diff --git a/shared/RFC822.m b/shared/RFC822.m new file mode 100644 index 0000000..f9d32c3 --- /dev/null +++ b/shared/RFC822.m @@ -0,0 +1,54 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "RFC822.h" +#import "SetNSError.h" +#import "RegexKitLite.h" + +#define FMT822 (@"^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})Z$") + +@implementation RFC822 ++ (NSDate *)dateFromString:(NSString *)dateString error:(NSError **)error { + if ([dateString rangeOfRegex:FMT822].location == NSNotFound) { + SETNSERROR(@"S3ErrorDomain", -1, @"invalid date '%@'", dateString); + return nil; + } + return [NSCalendarDate dateWithYear:[[dateString stringByMatching:FMT822 capture:1] intValue] + month:[[dateString stringByMatching:FMT822 capture:2] intValue] + day:[[dateString stringByMatching:FMT822 capture:3] intValue] + hour:[[dateString stringByMatching:FMT822 capture:4] intValue] + minute:[[dateString stringByMatching:FMT822 capture:5] intValue] + second:[[dateString stringByMatching:FMT822 capture:6] intValue] + timeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + +} +@end diff --git a/shared/RegexKitLite.h b/shared/RegexKitLite.h new file mode 100644 index 0000000..f69021b --- /dev/null +++ b/shared/RegexKitLite.h @@ -0,0 +1,186 @@ +// +// RegexKitLite.h +// http://regexkit.sourceforge.net/ +// Licensed under the terms of the BSD License, as specified below. +// + +/* + Copyright (c) 2008, John Engelhart + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the Zang Industries nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. +*/ + +#ifdef __OBJC__ +#import +#import +#import +#endif // __OBJC__ + +#include +#include +#include + +#ifndef REGEXKITLITE_VERSION_DEFINED +#define REGEXKITLITE_VERSION_DEFINED + +#define REGEXKITLITE_VERSION_MAJOR 2 +#define REGEXKITLITE_VERSION_MINOR 2 + +#define REGEXKITLITE_VERSION_CSTRING _RKL_VERSION_STRING(REGEXKITLITE_VERSION_MAJOR, REGEXKITLITE_VERSION_MINOR) +#define REGEXKITLITE_VERSION_NSSTRING @REGEXKITLITE_VERSION_CSTRING + +#define _RKL__STRINGIFY(b) #b +#define _RKL_STRINGIFY(a) _RKL__STRINGIFY(a) +#define _RKL_JOIN_VERSION(a,b) _RKL_STRINGIFY(a##.##b) +#define _RKL_VERSION_STRING(a,b) _RKL_JOIN_VERSION(a,b) + +#endif // REGEXKITLITE_VERSION_DEFINED + +#ifdef __cplusplus +extern "C" { +#endif + +// For Mac OS X < 10.5. +#ifndef NSINTEGER_DEFINED +#define NSINTEGER_DEFINED +#ifdef __LP64__ || NS_BUILD_32_LIKE_64 +typedef long NSInteger; +typedef unsigned long NSUInteger; +#define NSIntegerMin LONG_MIN +#define NSIntegerMax LONG_MAX +#define NSUIntegerMax ULONG_MAX +#else // 32-bit +typedef int NSInteger; +typedef unsigned int NSUInteger; +#define NSIntegerMin INT_MIN +#define NSIntegerMax INT_MAX +#define NSUIntegerMax UINT_MAX +#endif // __LP64__ || NS_BUILD_32_LIKE_64 +#endif // NSINTEGER_DEFINED + +#ifndef RKLREGEXOPTIONS_DEFINED +#define RKLREGEXOPTIONS_DEFINED + +// These must be idential to their ICU regex counterparts. See http://www.icu-project.org/userguide/regexp.html +enum { + RKLNoOptions = 0, + RKLCaseless = 2, + RKLComments = 4, + RKLDotAll = 32, + RKLMultiline = 8, + RKLUnicodeWordBoundaries = 256 +}; +typedef uint32_t RKLRegexOptions; + +#endif // RKLREGEXOPTIONS_DEFINED + +#ifndef _REGEXKITLITE_H_ +#define _REGEXKITLITE_H_ + +#ifdef __OBJC__ + +@class NSError; + +// NSException exception name. +extern NSString * const RKLICURegexException; + +// NSError error domains and user info keys. +extern NSString * const RKLICURegexErrorDomain; + +extern NSString * const RKLICURegexErrorCodeErrorKey; +extern NSString * const RKLICURegexErrorNameErrorKey; +extern NSString * const RKLICURegexLineErrorKey; +extern NSString * const RKLICURegexOffsetErrorKey; +extern NSString * const RKLICURegexPreContextErrorKey; +extern NSString * const RKLICURegexPostContextErrorKey; +extern NSString * const RKLICURegexRegexErrorKey; +extern NSString * const RKLICURegexRegexOptionsErrorKey; + +// If it looks like low memory notifications might be available, add code to register and respond to them. +// This is (should be) harmless if it turns out that this isn't the case, since the notification that we register for, +// UIApplicationDidReceiveMemoryWarningNotification, is dynamically looked up via dlsym(). +#if (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) && (!defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) || (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS != 0)) +#define RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS 1 +#endif + +#ifdef RKL_PREPEND_TO_METHODS +// This requires a few levels of rewriting to get the desired results. +#define RKL_METHOD_PREPEND_2(c,d) c ## d +#define RKL_METHOD_PREPEND_1(a,b) RKL_METHOD_PREPEND_2(a,b) +#define RKL_METHOD_PREPEND(x) RKL_METHOD_PREPEND_1(RKL_PREPEND_TO_METHODS, x) +#else +#define RKL_METHOD_PREPEND(x) x +#endif + +@interface NSString (RegexKitLiteAdditions) + ++ (void)RKL_METHOD_PREPEND(clearStringCache); + ++ (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex; ++ (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex options:(RKLRegexOptions)options error:(NSError **)error; + +- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex; +- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex range:(NSRange)range; +- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error; + +- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex; +- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex inRange:(NSRange)range; +- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error; + +- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex; +- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex capture:(NSInteger)capture; +- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex inRange:(NSRange)range; +- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error; + +- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex; +- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex capture:(NSInteger)capture; +- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex inRange:(NSRange)range; +- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error; + +- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement; +- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange; +- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error; + +@end + +@interface NSMutableString (RegexKitLiteAdditions) + +- (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement; +- (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange; +- (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error; + +@end + +#endif // __OBJC__ + +#endif // _REGEXKITLITE_H_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/shared/RegexKitLite.m b/shared/RegexKitLite.m new file mode 100644 index 0000000..6bf66af --- /dev/null +++ b/shared/RegexKitLite.m @@ -0,0 +1,1013 @@ +// +// RegexKitLite.m +// http://regexkit.sourceforge.net/ +// Licensed under the terms of the BSD License, as specified below. +// + +/* + Copyright (c) 2008, John Engelhart + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the Zang Industries nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. +*/ + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#ifdef __OBJC_GC__ +#import +#endif +#import +#import +#import +#import +#import +#import +#import "RegexKitLite.h" + +// Compile time tuneables. + +#ifndef RKL_CACHE_SIZE +#define RKL_CACHE_SIZE 23 +#endif + +#ifndef RKL_FIXED_LENGTH +#define RKL_FIXED_LENGTH 2048 +#endif + +#ifndef RKL_STACK_LIMIT +#define RKL_STACK_LIMIT (128 * 1024) +#endif + +#define SCRATCH_BUFFERS 4 + +// These macros are nearly identical to their NSCParameterAssert siblings. +// This is required because nearly everything is done while cacheSpinLock is locked. +// We need to safely unlock before throwing any of these exceptions. +// @try {} @finally {} significantly slows things down so it's not used. +#define RKLCAssert(d, ...) RKLCAssertDictionary(__PRETTY_FUNCTION__, __FILE__, __LINE__, (d), ##__VA_ARGS__) +#ifdef NS_BLOCK_ASSERTIONS +#define _RKLCDelayedAssertBody(c, e, g, d, ...) +#else +#define _RKLCDelayedAssertBody(c, e, g, d, ...) do { id *_e=(e); if(*_e!=NULL) { goto g; } if(!(c)) { *_e = RKLCAssert((d), ##__VA_ARGS__); goto g; } } while(0) +#endif // NS_BLOCK_ASSERTIONS +#define RKLCDelayedAssert(c, e, g) _RKLCDelayedAssertBody(c, e, g, @"Invalid parameter not satisfying: %s", #c) + +#define RKLRaiseException(e, f, ...) [[NSException exceptionWithName:(e) reason:RKLStringFromClassAndMethod((self), (_cmd), (f), ##__VA_ARGS__) userInfo:NULL] raise] + +// Ugly macros to keep other parts clean. + +#define NSMaxRange(r) ((r).location + (r).length) +#define NSRangeInsideRange(in, win) (((((in).location - (win).location) <= (win).length) && ((NSMaxRange(in) - (win).location) <= (win).length))) +#define NSEqualRanges(r1, r2) ((((r1).location == (r2).location) && ((r1).length == (r2).length))) +#define NSMakeRange(loc, len) ((NSRange){(NSUInteger)(loc), (NSUInteger)(len)}) +#define CFMakeRange(loc, len) ((CFRange){ (CFIndex)(loc), (CFIndex)(len)}) +#define NSNotFoundRange ((NSRange){NSNotFound, 0 }) +#define NSMaxiumRange ((NSRange){ 0, NSUIntegerMax}) + +#if defined (__GNUC__) && (__GNUC__ >= 4) +#define RKL_PREFETCH(ptr, off) { const char *p = ((const char *)(ptr)) + ((off) + 64); __builtin_prefetch(p); __builtin_prefetch(p + 64); } +#else +#define RKL_PREFETCH(ptr, off) +#endif + +// If the gcc flag -mmacosx-version-min is used with, for example, '=10.2', give a warning that the libicucore.dylib is only available on >= 10.3. +// If you are reading this comment because of this warning, this is to let you know that linking to /usr/lib/libicucore.dylib will cause your executable to fail on < 10.3. +// You will need to build your own version of the ICU library and link to that in order for RegexKitLite to work successfully on < 10.3. This is not simple. + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1030 +#warning The ICU dynamic shared library, /usr/lib/libicucore.dylib, is only available on Mac OS X 10.3 and later. +#warning You will need to supply a version of the ICU library to use RegexKitLite on Mac OS X 10.2 and earlier. +#endif + +#define RKLGetRangeForCapture(re, s, c, r) ({ int32_t start = uregex_start((re), (int32_t)(c), (s)); if(start == -1) { r = NSNotFoundRange; } else { r.location = (NSUInteger)start; r.length = (NSUInteger)uregex_end((re), (int32_t)(c), (s)) - r.location; } *(s); }) + +// Exported symbols. Exception names, error domains, keys, etc. +NSString * const RKLICURegexException = @"RKLICURegexException"; + +NSString * const RKLICURegexErrorDomain = @"RKLICURegexErrorDomain"; + +NSString * const RKLICURegexErrorCodeErrorKey = @"RKLICURegexErrorCode"; +NSString * const RKLICURegexErrorNameErrorKey = @"RKLICURegexErrorName"; +NSString * const RKLICURegexLineErrorKey = @"RKLICURegexLine"; +NSString * const RKLICURegexOffsetErrorKey = @"RKLICURegexOffset"; +NSString * const RKLICURegexPreContextErrorKey = @"RKLICURegexPreContext"; +NSString * const RKLICURegexPostContextErrorKey = @"RKLICURegexPostContext"; +NSString * const RKLICURegexRegexErrorKey = @"RKLICURegexRegex"; +NSString * const RKLICURegexRegexOptionsErrorKey = @"RKLICURegexRegexOptions"; + +// Type / struct definitions + +typedef struct uregex uregex; // Opaque ICU regex type. + +#define U_BUFFER_OVERFLOW_ERROR 15 + +#define U_PARSE_CONTEXT_LEN 16 + +typedef struct UParseError { + int32_t line; + int32_t offset; + UniChar preContext[U_PARSE_CONTEXT_LEN]; + UniChar postContext[U_PARSE_CONTEXT_LEN]; +} UParseError; + +enum { + RKLSplitOp = 1, + RKLReplaceOp = 2, + RKLRangeOp = 3, + RKLMaskOp = 0xf, + RKLReplaceMutable = 1 << 4, +}; +typedef NSUInteger RKLRegexOp; + +typedef struct { + CFStringRef string; + CFHashCode hash; + CFIndex length; + UniChar *uniChar; +} RKLBuffer; + +typedef struct { + CFStringRef regexString; + RKLRegexOptions options; + uregex *icu_regex; + NSInteger captureCount; + + CFStringRef setToString; + CFHashCode setToHash; + CFIndex setToLength; + NSUInteger setToIsImmutable:1; + NSUInteger setToNeedsConversion:1; + const UniChar *setToUniChar; + NSRange setToRange, lastFindRange, lastMatchRange; + NSUInteger pad[1]; // For 32 bits, this makes the struct 64 bytes exactly, which is good for cache line alignment. +} RKLCacheSlot; + +// ICU functions. See http://www.icu-project.org/apiref/icu4c/uregex_8h.html Tweaked slightly from the originals, but functionally identical. +const char *u_errorName (int32_t status); +int32_t u_strlen (const UniChar *s); +int32_t uregex_appendReplacement (uregex *regexp, const UniChar *replacementText, int32_t replacementLength, UniChar **destBuf, int32_t *destCapacity, int32_t *status); +int32_t uregex_appendTail (uregex *regexp, UniChar **destBuf, int32_t *destCapacity, int32_t *status); +void uregex_close (uregex *regexp); +int32_t uregex_end (uregex *regexp, int32_t groupNum, int32_t *status); +BOOL uregex_find (uregex *regexp, int32_t location, int32_t *status); +BOOL uregex_findNext (uregex *regexp, int32_t *status); +int32_t uregex_groupCount (uregex *regexp, int32_t *status); +uregex *uregex_open (const UniChar *pattern, int32_t patternLength, RKLRegexOptions flags, UParseError *parseError, int32_t *status); +void uregex_reset (uregex *regexp, int32_t newIndex, int32_t *status); +void uregex_setText (uregex *regexp, const UniChar *text, int32_t textLength, int32_t *status); +int32_t uregex_split (uregex *regexp, UniChar *destBuf, int32_t destCapacity, int32_t *requiredCapacity, UniChar *destFields[], int32_t destFieldsCapacity, int32_t *status); +int32_t uregex_start (uregex *regexp, int32_t groupNum, int32_t *status); + + +static RKLCacheSlot *getCachedRegex (NSString *regexString, RKLRegexOptions options, NSError **error, id *exception); +static BOOL setCacheSlotToString (RKLCacheSlot *cacheSlot, const NSRange *range, int32_t *status, id *exception); +static RKLCacheSlot *getCachedRegexSetToString (NSString *regexString, RKLRegexOptions options, NSString *matchString, NSUInteger *matchLengthPtr, NSRange *matchRange, NSError **error, id *exception, int32_t *status); +static id performRegexOp (id self, SEL _cmd, RKLRegexOp doRegexOp, NSString *regexString, RKLRegexOptions options, NSInteger capture, id matchString, NSRange *matchRange, NSString *replacementString, NSError **error, void **result); + +static void rkl_find (RKLCacheSlot *cacheSlot, NSInteger capture, NSRange searchRange, NSRange *resultRange, id *exception, int32_t *status); +static NSArray *rkl_splitArray (RKLCacheSlot *cacheSlot, id *exception, int32_t *status); +static NSString *rkl_replaceString (RKLCacheSlot *cacheSlot, id searchString, NSUInteger searchU16Length, NSString *replacementString, NSUInteger replacementU16Length, NSUInteger *replacedCount, int replaceMutable, id *exception, int32_t *status); +static int32_t rkl_replaceAll (RKLCacheSlot *cacheSlot, const UniChar *replacementUniChar, int32_t replacementU16Length, UniChar *replacedUniChar, int32_t replacedU16Capacity, NSUInteger *replacedCount, id *exception, int32_t *status); + +static void rkl_clearStringCache (void); +static void clearBuffer (RKLBuffer *buffer, int freeDynamicBuffer); +static void clearCacheSlotRegex (RKLCacheSlot *cacheSlot); +static void clearCacheSlotSetTo (RKLCacheSlot *cacheSlot); + +static NSDictionary *userInfoDictionary (NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int status, ...); +static NSError *RKLNSErrorForRegex (NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int status); +static NSException *RKLNSExceptionForRegex (NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int status); +static NSDictionary *RKLCAssertDictionary (const char *function, const char *file, int line, NSString *format, ...); +static NSString *RKLStringFromClassAndMethod(id object, SEL selector, NSString *format, ...); + +#ifdef __OBJC_GC__ +// If compiled with Garbage Collection, we need to be able to do a few things slightly differently. +// The basic premiss is that under GC we use a trampoline function pointer which is set to a _start function to catch the first invocation. +// The _start function checks if GC is running and then overwrites the function pointer with the appropriate routine. Think of it as 'lazy linking'. + +// rkl_collectingEnabled uses objc_getClass() to get the NSGarbageCollector class, which doesn't exist on earlier systems. +// This allows for graceful failure should we find ourselves running on an earlier version of the OS without NSGarbageCollector. +static BOOL rkl_collectingEnabled_first (void); +static BOOL rkl_collectingEnabled_yes (void) { return(YES); } +static BOOL rkl_collectingEnabled_no (void) { return(NO); } +static BOOL(*rkl_collectingEnabled) (void) = rkl_collectingEnabled_first; +static BOOL rkl_collectingEnabled_first (void) { return((([objc_getClass("NSGarbageCollector") defaultCollector]!=NULL) ? (rkl_collectingEnabled=rkl_collectingEnabled_yes) : (rkl_collectingEnabled=rkl_collectingEnabled_no))()); } + +static void *rkl_realloc_first (void **ptr, size_t size, NSUInteger flags); +static void *rkl_realloc_std (void **ptr, size_t size, NSUInteger flags) { flags=flags; /*unused*/ return((*ptr = reallocf(*ptr, size))); } +static void *rkl_realloc_gc (void **ptr, size_t size, NSUInteger flags) { void *p=NULL; if(flags!=0) { p=NSAllocateCollectable((NSUInteger)size,flags); if(*ptr!=NULL) { free(*ptr); *ptr=NULL; } } else { p=*ptr=reallocf(*ptr, size); } return(p); } +static void *(*rkl_realloc) (void **ptr, size_t size, NSUInteger flags) = rkl_realloc_first; +static void *rkl_realloc_first (void **ptr, size_t size, NSUInteger flags) { return(((rkl_collectingEnabled()==YES) ? (rkl_realloc=rkl_realloc_gc) : (rkl_realloc=rkl_realloc_std))(ptr, size, flags)); } + +static id rkl_CFAutorelease_first (CFTypeRef obj); +static id rkl_CFAutorelease_std (CFTypeRef obj) { return([(id)obj autorelease]); } +static id rkl_CFAutorelease_gc (CFTypeRef obj) { return((id)CFMakeCollectable(obj)); } +static id(*rkl_CFAutorelease) (CFTypeRef obj) = rkl_CFAutorelease_first; +static id rkl_CFAutorelease_first (CFTypeRef obj) { return(((rkl_collectingEnabled()==YES) ? (rkl_CFAutorelease=rkl_CFAutorelease_gc) : (rkl_CFAutorelease=rkl_CFAutorelease_std))(obj)); } + +#else // __OBJC_GC__ not defined + +static void *rkl_realloc (void **ptr, size_t size, NSUInteger flags) { flags=flags; /*unused*/ return((*ptr = reallocf(*ptr, size))); } +static id rkl_CFAutorelease (CFTypeRef obj) { return([(id)obj autorelease]); } + +#endif // __OBJC_GC__ + +#ifdef RKL_FAST_MUTABLE_CHECK +// We use a trampoline function pointer to check at run time if the function __CFStringIsMutable is available. +// If it is, the trampoline function pointer is replaced with the address of that function. +// Otherwise, we assume the worst case that ever string is mutable. +// This hopefully helps to protect us since we're using an undocumented, non-public API call. +// We will keep on working if it ever does go away, just with a bit less performance due to the overhead of mutable checks. +static BOOL rkl_CFStringIsMutable_first (CFStringRef str); +static BOOL rkl_CFStringIsMutable_yes (CFStringRef str) { str=str; /*unused*/ return(YES); } +static BOOL(*rkl_CFStringIsMutable) (CFStringRef str) = rkl_CFStringIsMutable_first; +static BOOL rkl_CFStringIsMutable_first (CFStringRef str) { if((rkl_CFStringIsMutable = dlsym(RTLD_DEFAULT, "__CFStringIsMutable")) == NULL) { rkl_CFStringIsMutable = rkl_CFStringIsMutable_yes; } return(rkl_CFStringIsMutable(str)); } +#else // RKL_FAST_MUTABLE_CHECK is not defined. Assume that all strings are potentially mutable. +#define rkl_CFStringIsMutable(s) (YES) +#endif +BOOL __CFStringIsMutable(CFStringRef str); + +// Translation unit scope global variables. + +static UniChar fixedUniChar[(RKL_FIXED_LENGTH)]; // This is the fixed sized UTF-16 conversion buffer. +static RKLCacheSlot RKLCache[(RKL_CACHE_SIZE)], *lastCacheSlot; +static OSSpinLock cacheSpinLock = OS_SPINLOCK_INIT; +static RKLBuffer dynamicBuffer, fixedBuffer = {NULL, 0UL, 0L, &fixedUniChar[0]}; +static const UniChar emptyUniCharString[1]; // For safety, icu_regexes are 'set' to this when the string they were searched is cleared. +static void *scratchBuffer[(SCRATCH_BUFFERS)]; // Used to hold temporary allocations that are allocated via reallocf(). + +// These are used when running under manual memory management for the array that rkl_splitArray creates. +// The split strings are created, but not autoreleased. The (immutable) array is created using these callbacks, which skips the CFRetain() call. +// For each split string this saves the overhead of an autorelease, then an array retain, then a autoreleasepool release. This is good for a ~30% speed increase. +static Boolean RKLCFArrayEqualCallBack (const void *value1, const void *value2) { return(CFEqual(value1, value2)); } +static void RKLCFArrayRelease (CFAllocatorRef allocator, const void *ptr) { allocator=allocator;/*unused*/ CFRelease(ptr); } +static CFArrayCallBacks transferOwnershipArrayCallBacks = { 0, NULL, RKLCFArrayRelease, NULL, RKLCFArrayEqualCallBack }; + +#if defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) && (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS == 1) + +// The next few lines are specifically for the iPhone to catch low memory conditions. +// The basic idea is that rkl_RegisterForLowMemoryNotifications() is set to be run once by the linker at load time via __attribute((constructor)). +// rkl_RegisterForLowMemoryNotifications() tries to find the iPhone low memory notification symbol. If it can find it, +// it registers with the default NSNotificationCenter to call the RKLLowMemoryWarningObserver class method +lowMemoryWarning:. +// rkl_RegisterForLowMemoryNotifications() uses an atomic compare and swap to guarentee that it initalizes exactly once. +// +lowMemoryWarning tries to acquire the cache lock. If it gets the lock, it clears the cache. If it can't, it calls performSelector: +// with a delay of half a second to try again. This will hopefully prevent any deadlocks, such as a RegexKitLite request for +// memory triggering a notifcation while the lock is held. + +static void rkl_RegisterForLowMemoryNotifications(void); + +@interface RKLLowMemoryWarningObserver : NSObject +(void)lowMemoryWarning:(id)notification; @end +@implementation RKLLowMemoryWarningObserver ++(void)lowMemoryWarning:(id)notification { + if(OSSpinLockTry(&cacheSpinLock)) { rkl_clearStringCache(); OSSpinLockUnlock(&cacheSpinLock); } + else { [[RKLLowMemoryWarningObserver class] performSelector:@selector(lowMemoryWarning:) withObject:NULL afterDelay:0.5]; } +} +@end + +static int rkl_HaveRegisteredForLowMemoryNotifications = 0; + +__attribute__((constructor)) static void rkl_RegisterForLowMemoryNotifications(void) { + void **memoryWarningNotification = NULL; + + if(OSAtomicCompareAndSwapIntBarrier(0, 1, &rkl_HaveRegisteredForLowMemoryNotifications)) { + if((memoryWarningNotification = dlsym(RTLD_DEFAULT, "UIApplicationDidReceiveMemoryWarningNotification")) != NULL) { + [[NSNotificationCenter defaultCenter] addObserver:[RKLLowMemoryWarningObserver class] selector:@selector(lowMemoryWarning:) name:*memoryWarningNotification object:NULL]; + } + } +} + +#endif + +// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. +// IMPORTANT! Should only be called with cacheSpinLock already locked! +// ---------- + +static RKLCacheSlot *getCachedRegex(NSString *regexString, RKLRegexOptions options, NSError **error, id *exception) { + RKLCacheSlot *cacheSlot = NULL; + CFHashCode regexHash = 0; + int32_t status = 0; + + RKLCDelayedAssert(regexString != NULL, exception, exitNow); + + // Fast path the common case where this regex is exactly the same one used last time. + if((lastCacheSlot != NULL) && (lastCacheSlot->options == options) && (lastCacheSlot->icu_regex != NULL) && (lastCacheSlot->regexString != NULL) && (lastCacheSlot->regexString == (CFStringRef)regexString)) { return(lastCacheSlot); } + + regexHash = CFHash((CFTypeRef)regexString); + cacheSlot = &RKLCache[(regexHash % RKL_CACHE_SIZE)]; // Retrieve the cache slot for this regex. + + // Return the cached entry if it's a match, otherwise clear the slot and create a new ICU regex in its place. + if((cacheSlot->options == options) && (cacheSlot->icu_regex != NULL) && (cacheSlot->regexString != NULL) && ((cacheSlot->regexString == (CFStringRef)regexString) || (CFEqual((CFTypeRef)regexString, cacheSlot->regexString) == YES))) { lastCacheSlot = cacheSlot; return(cacheSlot); } + + clearCacheSlotRegex(cacheSlot); + + if((cacheSlot->regexString = CFStringCreateCopy(NULL, (CFStringRef)regexString)) == NULL) { goto exitNow; } ; // Get a cheap immutable copy. + cacheSlot->options = options; + + CFIndex regexStringU16Length = CFStringGetLength(cacheSlot->regexString); // In UTF16 code units. + UParseError parseError = (UParseError){-1, -1, {0}, {0}}; + UniChar *regexUniChar = NULL; + + // Try to quickly obtain regexString in UTF16 format. + if((regexUniChar = (UniChar *)CFStringGetCharactersPtr(cacheSlot->regexString)) == NULL) { // We didn't get the UTF16 pointer quickly and need to perform a full conversion in a temp buffer. + if((regexStringU16Length * sizeof(UniChar)) < RKL_STACK_LIMIT) { if((regexUniChar = alloca(regexStringU16Length * sizeof(UniChar))) == NULL) { goto exitNow; } } // Try to use the stack. + else { if((regexUniChar = rkl_realloc(&scratchBuffer[0], regexStringU16Length * sizeof(UniChar), 0UL)) == NULL) { goto exitNow; } } // Otherwise use the heap. + CFStringGetCharacters(cacheSlot->regexString, CFMakeRange(0, regexStringU16Length), (UniChar *)regexUniChar); // Convert regexString to UTF16. + } + + // Create the ICU regex. + if((cacheSlot->icu_regex = uregex_open(regexUniChar, (int32_t)regexStringU16Length, options, &parseError, &status)) == NULL) { goto exitNow; } + if(status <= 0) { cacheSlot->captureCount = (NSInteger)uregex_groupCount(cacheSlot->icu_regex, &status); } + if(status <= 0) { lastCacheSlot = cacheSlot; } + + exitNow: + if(scratchBuffer[0] != NULL) { free(scratchBuffer[0]); scratchBuffer[0] = NULL; } + if(status > 0) { cacheSlot = NULL; if(error != NULL) { *error = RKLNSErrorForRegex(regexString, options, &parseError, status); } } + return(cacheSlot); +} + +// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. +// IMPORTANT! Should only be called with cacheSpinLock already locked! +// ---------- + +static BOOL setCacheSlotToString(RKLCacheSlot *cacheSlot, const NSRange *range, int32_t *status, id *exception) { + RKLCDelayedAssert((cacheSlot != NULL) && (cacheSlot->setToString != NULL) && (range != NULL) && (status != NULL), exception, exitNow); + + if(cacheSlot->setToNeedsConversion == NO) { goto setRegexText; } + + RKLBuffer *buffer = (cacheSlot->setToLength < RKL_FIXED_LENGTH) ? &fixedBuffer : &dynamicBuffer; + if((cacheSlot->setToUniChar != NULL) && ((cacheSlot->setToString == buffer->string) || ((cacheSlot->setToLength == buffer->length) && (cacheSlot->setToHash == buffer->hash)))) { goto setRegexText; } + + clearBuffer(buffer, NO); + + if(cacheSlot->setToLength >= RKL_FIXED_LENGTH) { + RKLCDelayedAssert(buffer == &dynamicBuffer, exception, exitNow); + if((dynamicBuffer.uniChar = rkl_realloc((void *)&dynamicBuffer.uniChar, (cacheSlot->setToLength * sizeof(UniChar)), 0UL)) == NULL) { return(NO); } // Resize the buffer. + } + RKLCDelayedAssert(buffer->uniChar != NULL, exception, exitNow); + CFStringGetCharacters(cacheSlot->setToString, CFMakeRange(0, cacheSlot->setToLength), (UniChar *)buffer->uniChar); // Convert to a UTF16 string. + + if((buffer->string = CFRetain(cacheSlot->setToString)) == NULL) { return(NO); } + buffer->hash = cacheSlot->setToHash; + buffer->length = cacheSlot->setToLength; + + cacheSlot->setToUniChar = buffer->uniChar; + cacheSlot->setToRange = NSNotFoundRange; + + setRegexText: + + if(NSEqualRanges(cacheSlot->setToRange, *range) == NO) { + RKLCDelayedAssert((cacheSlot->icu_regex != NULL) && (cacheSlot->setToUniChar != NULL) && (NSMaxRange(*range) <= (NSUInteger)cacheSlot->setToLength), exception, exitNow); + cacheSlot->lastFindRange = cacheSlot->lastMatchRange = NSNotFoundRange; + cacheSlot->setToRange = *range; + uregex_setText(cacheSlot->icu_regex, cacheSlot->setToUniChar + cacheSlot->setToRange.location, (int32_t)cacheSlot->setToRange.length, status); + if(*status > 0) { return(NO); } + } + + return(YES); + + exitNow: + return(NO); +} + +// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. +// IMPORTANT! Should only be called with cacheSpinLock already locked! +// ---------- + +static RKLCacheSlot *getCachedRegexSetToString(NSString *regexString, RKLRegexOptions options, NSString *matchString, NSUInteger *matchLengthPtr, NSRange *matchRange, NSError **error, id *exception, int32_t *status) { + RKLCacheSlot *cacheSlot = NULL; + RKLCDelayedAssert((regexString != NULL) && (exception != NULL) && (status != NULL), exception, exitNow); + + // Fast path the common case where this regex is exactly the same one used last time. + if((lastCacheSlot != NULL) && (lastCacheSlot->regexString == (CFStringRef)regexString) && (lastCacheSlot->options == options)) { cacheSlot = lastCacheSlot; } + else { if((cacheSlot = getCachedRegex(regexString, options, error, exception)) == NULL) { goto exitNow; } } + + // Optimize the case where the string to search (matchString) is immutable and the setToString immutable copy is the same string with its reference count incremented. + BOOL isSetTo = ((cacheSlot->setToString != NULL) && (cacheSlot->setToString == (CFStringRef)matchString)) ? YES : NO; + CFIndex matchLength = ((isSetTo == YES) && (cacheSlot->setToIsImmutable == YES)) ? cacheSlot->setToLength : CFStringGetLength((CFStringRef)matchString); + + *matchLengthPtr = (NSUInteger)matchLength; + if(matchRange->length == NSUIntegerMax) { matchRange->length = matchLength; } // For convenience, allow NSUIntegerMax == string length. + + if((NSUInteger)matchLength < NSMaxRange(*matchRange)) { *exception = [NSException exceptionWithName:NSRangeException reason:@"Range or index out of bounds" userInfo:NULL]; goto exitNow; } + + if((cacheSlot->setToIsImmutable == NO) && (cacheSlot->setToString != NULL) && ((cacheSlot->setToLength != CFStringGetLength(cacheSlot->setToString)) || (cacheSlot->setToHash != CFHash(cacheSlot->setToString)))) { isSetTo = NO; } + else { // If the first pointer equality check failed, check the hash and length. + if(((isSetTo == NO) || (cacheSlot->setToIsImmutable == NO)) && (cacheSlot->setToString != NULL)) { isSetTo = ((cacheSlot->setToLength == matchLength) && (cacheSlot->setToHash == CFHash((CFStringRef)(matchString)))); } + + if((isSetTo == YES)) { // Make sure that the UTF16 conversion cache is set to this string, if conversion is required. + if((cacheSlot->setToNeedsConversion == YES) && (setCacheSlotToString(cacheSlot, matchRange, status, exception) == NO)) { *exception = RKLCAssert(@"Failed to set up UTF16 buffer."); goto exitNow; } + if(NSEqualRanges(cacheSlot->setToRange, *matchRange) == YES) { goto exitNow; } // Verify that the range to search is what the cached regex was prepped for last time. + } + } + + // Sometimes the range that the regex is set to isn't right, in which case we don't want to clear the cache slot. Otherwise, flush it out. + if((cacheSlot->setToString != NULL) && (isSetTo == NO)) { clearCacheSlotSetTo(cacheSlot); } + + if(cacheSlot->setToString == NULL) { + cacheSlot->setToString = CFRetain(matchString); + RKLCDelayedAssert(cacheSlot->setToString != NULL, exception, exitNow); + cacheSlot->setToUniChar = CFStringGetCharactersPtr(cacheSlot->setToString); + cacheSlot->setToNeedsConversion = (cacheSlot->setToUniChar == NULL) ? YES : NO; + cacheSlot->setToIsImmutable = !rkl_CFStringIsMutable(cacheSlot->setToString); // If RKL_FAST_MUTABLE_CHECK is not defined then the result is '0', or in other words mutable.. + cacheSlot->setToHash = CFHash(cacheSlot->setToString); + cacheSlot->setToRange = NSNotFoundRange; + cacheSlot->setToLength = matchLength; + } + + if(setCacheSlotToString(cacheSlot, matchRange, status, exception) == NO) { cacheSlot = NULL; goto exitNow; } + + exitNow: + return(cacheSlot); +} + +// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. +// ---------- + +static id performRegexOp(id self, SEL _cmd, RKLRegexOp doRegexOp, NSString *regexString, RKLRegexOptions options, NSInteger capture, id matchString, NSRange *matchRange, NSString *replacementString, NSError **error, void **result) { + BOOL replaceMutable = ((doRegexOp & RKLReplaceMutable) != 0) ? YES : NO; + RKLRegexOp regexOp = (doRegexOp & RKLMaskOp); + + if((error != NULL) && (*error != NULL)) { *error = NULL; } + + if(regexString == NULL) { RKLRaiseException(NSInvalidArgumentException, @"The regular expression argument is NULL."); } + if(matchString == NULL) { RKLRaiseException(NSInternalInconsistencyException, @"The match string argument is NULL."); } + if((regexOp == RKLReplaceOp) && (replacementString == NULL)) { RKLRaiseException(NSInvalidArgumentException, @"The replacement string argument is NULL."); } + + NSUInteger stringU16Length = 0UL, replacementU16Length = (NSUInteger)((replacementString != NULL) ? CFStringGetLength((CFStringRef)replacementString) : 0); // In UTF16 code units. + NSRange stringRange = NSMakeRange(0, NSUIntegerMax), searchRange = (matchRange != NULL) ? *matchRange : NSNotFoundRange; + RKLCacheSlot *cacheSlot = NULL; + id exception = NULL; + id resultObject = NULL; + int32_t status = 0; + + // IMPORTANT! Once we have obtained the lock, code MUST exit via 'goto exitNow;' to unlock the lock! NO EXCEPTIONS! + // ---------- + OSSpinLockLock(&cacheSpinLock); // Grab the lock and get cache entry. + + if(((cacheSlot = getCachedRegexSetToString(regexString, options, matchString, &stringU16Length, (regexOp == RKLRangeOp) ? &stringRange : &searchRange, error, &exception, &status)) == NULL) || (exception != NULL) || (status > 0)) { goto exitNow; } + + if(searchRange.length == NSUIntegerMax) { searchRange.length = stringU16Length; } // For convenience. + if(stringU16Length < NSMaxRange(searchRange)) { exception = [NSException exceptionWithName:NSRangeException reason:@"Range or index out of bounds" userInfo:NULL]; goto exitNow; } + + RKLCDelayedAssert((cacheSlot->icu_regex != NULL) && (exception == NULL), &exception, exitNow); + + if(cacheSlot->setToNeedsConversion != 0) { + RKLBuffer *buffer = (cacheSlot->setToLength < RKL_FIXED_LENGTH) ? &fixedBuffer : &dynamicBuffer; + RKLCDelayedAssert((cacheSlot->setToHash == buffer->hash) && (cacheSlot->setToLength == buffer->length) && (cacheSlot->setToUniChar == buffer->uniChar), &exception, exitNow); + } + + switch(regexOp) { + case RKLRangeOp: rkl_find(cacheSlot, capture, searchRange, (NSRange *)result, &exception, &status); break; + case RKLSplitOp: resultObject = rkl_splitArray(cacheSlot, &exception, &status); break; + case RKLReplaceOp: resultObject = rkl_replaceString(cacheSlot, matchString, stringU16Length, replacementString, replacementU16Length, (NSUInteger *)result, replaceMutable, &exception, &status); break; + default: exception = RKLCAssert(@"Unknown regexOp code."); break; + } + + exitNow: + OSSpinLockUnlock(&cacheSpinLock); + + if((status > 0) && (exception == NULL)) { exception = RKLNSExceptionForRegex(regexString, options, NULL, status); } // If we had a problem, throw an exception. + if(exception != NULL) { + if([exception isKindOfClass:[NSException class]]) { [[NSException exceptionWithName:[exception name] reason:RKLStringFromClassAndMethod(self, _cmd, [exception reason]) userInfo:[exception userInfo]] raise]; } + else { [[NSAssertionHandler currentHandler] handleFailureInFunction:[exception objectForKey:@"function"] file:[exception objectForKey:@"file"] lineNumber:[[exception objectForKey:@"line"] longValue] description:[exception objectForKey:@"description"]]; } + } + if(replaceMutable == YES) { // We're working on a mutable string and if there were successfull matches with replaced text we still have work to do. Done outside the cache lock. + if(*((NSUInteger *)result) > 0) { NSCParameterAssert(resultObject != NULL); [matchString replaceCharactersInRange:searchRange withString:resultObject]; } + } + + return(resultObject); +} + +// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. +// IMPORTANT! Should only be called from performRegexOp(). +// ---------- + +static void rkl_find(RKLCacheSlot *cacheSlot, NSInteger capture, NSRange searchRange, NSRange *resultRange, id *exception, int32_t *status) { + NSRange captureRange = NSNotFoundRange; + + RKLCDelayedAssert((cacheSlot != NULL) && (resultRange != NULL) && (exception != NULL) && (status != NULL), exception, exitNow); + + if((capture < 0) || (capture > cacheSlot->captureCount)) { *exception = [NSException exceptionWithName:NSInvalidArgumentException reason:@"The capture argument is not valid." userInfo:NULL]; goto exitNow; } + + if((NSEqualRanges(searchRange, cacheSlot->lastFindRange) == NO)) { // Only perform an expensive 'find' operation iff the current find range is different than the last find range. + RKL_PREFETCH(cacheSlot->setToUniChar, searchRange.location << 1); // Spool up the CPU caches. + + // Using uregex_findNext can be a slight performance win. + BOOL useFindNext = (searchRange.location == (NSMaxRange(cacheSlot->lastMatchRange) + ((cacheSlot->lastMatchRange.length == 0) ? 1 : 0))) ? YES : NO; + + cacheSlot->lastFindRange = NSNotFoundRange; // Cleared the cached search/find range. + if(useFindNext == NO) { if((uregex_find (cacheSlot->icu_regex, (int32_t)searchRange.location, status) == NO) || (*status > 0)) { goto exitNow; } } + else { if((uregex_findNext(cacheSlot->icu_regex, status) == NO) || (*status > 0)) { goto exitNow; } } + + if(RKLGetRangeForCapture(cacheSlot->icu_regex, status, 0, cacheSlot->lastMatchRange) != 0) { goto exitNow; } + if(NSRangeInsideRange(cacheSlot->lastMatchRange, searchRange) == NO) { goto exitNow; } // If the regex matched outside the requested range, exit. + + cacheSlot->lastFindRange = searchRange; // Cache the successful search/find range. + } + + if(capture == 0) { captureRange = cacheSlot->lastMatchRange; } else { RKLGetRangeForCapture(cacheSlot->icu_regex, status, capture, captureRange); } + + exitNow: + *resultRange = captureRange; +} + +// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. +// IMPORTANT! Should only be called from performRegexOp(). +// ---------- + +static NSArray *rkl_splitArray(RKLCacheSlot *cacheSlot, id *exception, int32_t *status) { + NSArray *resultArray = NULL; + + RKLCDelayedAssert((cacheSlot != NULL) && (status != NULL), exception, exitNow); + + const char *setToUniCharChar = (const char *)(cacheSlot->setToUniChar + cacheSlot->setToRange.location); + NSUInteger splitRangesCapacity = ((((RKL_STACK_LIMIT / sizeof(NSRange)) / 4) + ((cacheSlot->captureCount + 1) * 2)) + 2), splitRangesIndex = 0, lastLocation = 0, x = 0; + size_t splitRangesSize = (splitRangesCapacity * sizeof(NSRange)), stackUsed = 0; + NSInteger captureCount = cacheSlot->captureCount; + uregex *icu_regex = cacheSlot->icu_regex; + NSRange *splitRanges = NULL; + BOOL copiedStackToHeap = NO; + + if(cacheSlot->setToLength == 0) { resultArray = [NSArray array]; goto exitNow; } // Return an empty array when there is nothing to search. + + if(splitRangesSize < RKL_STACK_LIMIT) { if((splitRanges = alloca(splitRangesSize)) == NULL) { goto exitNow; } stackUsed += splitRangesSize; } + else { if((splitRanges = rkl_realloc(&scratchBuffer[0], splitRangesSize, 0UL)) == NULL) { goto exitNow; } } + + cacheSlot->lastFindRange = cacheSlot->lastMatchRange = NSNotFoundRange; // Clear the cached find information for this regex so a subsequent find works correctly. + uregex_reset(icu_regex, 0, status); // Reset the regex to the start of the string. + + for(splitRangesIndex = 0; splitRangesIndex < splitRangesCapacity; splitRangesIndex++) { + + if(splitRangesIndex >= ((splitRangesCapacity - ((captureCount + 1) * 2)) - 1)) { // Check if we need to grow our NSRanges buffer. + NSUInteger newCapacity = (((splitRangesCapacity + (splitRangesCapacity / 2)) + ((captureCount + 1) * 2)) + 2); + size_t newSize = (newCapacity * sizeof(NSRange)); + NSRange *newRanges = NULL; + + if((newRanges = rkl_realloc(&scratchBuffer[0], newSize, 0UL)) == NULL) { goto exitNow; } // We only try to use the stack the first time, after that, we use the heap. + if((stackUsed > 0) && (copiedStackToHeap == NO)) { memcpy(newRanges, splitRanges, splitRangesSize); copiedStackToHeap = YES; } + + splitRangesCapacity = newCapacity; + splitRangesSize = newSize; + splitRanges = newRanges; + } + + RKL_PREFETCH(setToUniCharChar, lastLocation << 1); // Spool up the CPU caches. + + NSUInteger baseMatchIndex = splitRangesIndex; + NSRange tempRange; + + if((uregex_findNext(icu_regex, status) == NO) || (*status > 0)) { break; } + if(RKLGetRangeForCapture(icu_regex, status, 0, tempRange) > 0) { goto exitNow; } + + splitRanges[splitRangesIndex] = NSMakeRange(lastLocation, tempRange.location - lastLocation); + lastLocation = NSMaxRange(tempRange); + + int32_t capture; + for(capture = 1; capture <= captureCount; capture++) { + RKLCDelayedAssert(splitRangesIndex < (splitRangesCapacity - 2), exception, exitNow); + splitRangesIndex++; + + if(RKLGetRangeForCapture(icu_regex, status, capture, splitRanges[splitRangesIndex]) > 0) { goto exitNow; } + if(splitRanges[splitRangesIndex].location == NSNotFound) { splitRanges[splitRangesIndex] = NSMakeRange(splitRanges[baseMatchIndex].location, 0); } + } + } + + RKLCDelayedAssert(splitRangesIndex < (splitRangesCapacity - 2), exception, exitNow); + splitRanges[splitRangesIndex] = NSMakeRange(lastLocation, (NSMaxRange(cacheSlot->setToRange) - cacheSlot->setToRange.location) - lastLocation); + splitRangesIndex++; + + CFIndex setToLocation = cacheSlot->setToRange.location; + CFStringRef setToString = cacheSlot->setToString; + size_t splitStringsSize = (splitRangesIndex * sizeof(id)); + id *splitStrings = NULL; + + if((stackUsed + splitStringsSize) < RKL_STACK_LIMIT) { if((splitStrings = alloca(splitStringsSize)) == NULL) { goto exitNow; } stackUsed += splitStringsSize; } +#ifdef __OBJC_GC__ + else { if((splitStrings = rkl_realloc(&scratchBuffer[1], splitStringsSize, (NSUInteger)NSScannedOption)) == NULL) { goto exitNow; } } +#else + // http://sourceforge.net/tracker/index.php?func=detail&aid=2050825&group_id=204582&atid=990188 + // This is to get around an iPhone quirk. For whatever reason, the iPhone NSZone.h explicitly removes all NSAllocateCollectable() + // bits and pieces using #if pre-processor conditions. Since NSScannedOption is only really used when the compiler has -fobjc-gc enabled, + // we just chop it out here. + else { if((splitStrings = rkl_realloc(&scratchBuffer[1], splitStringsSize, 0)) == NULL) { goto exitNow; } } +#endif + +#ifdef __OBJC_GC__ + if(rkl_collectingEnabled() == YES) { // I just don't trust the GC system with the faster CF way of doing things... It never seems to work quite the way you expect it to. + for(x = 0; x < splitRangesIndex; x++) { // Optimize the case where the length == 0 by substituting the string @"". + splitStrings[x] = (splitRanges[x].length == 0) ? @"" : [(id)setToString substringWithRange:NSMakeRange(setToLocation + splitRanges[x].location, splitRanges[x].length)]; + } + resultArray = [NSArray arrayWithObjects:splitStrings count:splitRangesIndex]; + } else +#endif + { // This block of code is always compiled in. It is used when not compiled with GC or when compiled with GC but the collector is not enabled. + for(x = 0; x < splitRangesIndex; x++) { // Optimize the case where the length == 0 by substituting the string @"". + splitStrings[x] = (splitRanges[x].length == 0) ? @"" : (id)CFStringCreateWithSubstring(NULL, setToString, CFMakeRange(setToLocation + splitRanges[x].location, (CFIndex)splitRanges[x].length)); + } + resultArray = rkl_CFAutorelease(CFArrayCreate(NULL, (const void **)splitStrings, (CFIndex)splitRangesIndex, &transferOwnershipArrayCallBacks)); // Create the CF/NSArray of the split strings. + } + + exitNow: + if(scratchBuffer[0] != NULL) { free(scratchBuffer[0]); scratchBuffer[0] = NULL; } + if(scratchBuffer[1] != NULL) { free(scratchBuffer[1]); scratchBuffer[1] = NULL; } + + return(resultArray); +} + +// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. +// IMPORTANT! Should only be called from performRegexOp(). +// ---------- + +static NSString *rkl_replaceString(RKLCacheSlot *cacheSlot, id searchString, NSUInteger searchU16Length, NSString *replacementString, NSUInteger replacementU16Length, NSUInteger *replacedCountPtr, int replaceMutable, id *exception, int32_t *status) { + int32_t resultU16Length = 0, tempUniCharBufferU16Capacity = 0; + UniChar *tempUniCharBuffer = NULL; + const UniChar *replacementUniChar = NULL; + id resultObject = NULL; + NSUInteger replacedCount = 0; + + // Zero order approximation of the buffer sizes for holding the replaced string or split strings and split strings pointer offsets. As UTF16 code units. + tempUniCharBufferU16Capacity = (int32_t)(16 + (searchU16Length + (searchU16Length >> 1)) + (replacementU16Length * 2)); + + // Buffer sizes converted from native units to bytes. + size_t stackSize = 0, replacementSize = (replacementU16Length * sizeof(UniChar)), tempUniCharBufferSize = (tempUniCharBufferU16Capacity * sizeof(UniChar)); + + // For the various buffers we require, we first try to allocate from the stack if we're not over the RKL_STACK_LIMIT. If we are, switch to using the heap for the buffer. + + if((stackSize + tempUniCharBufferSize) < RKL_STACK_LIMIT) { if((tempUniCharBuffer = alloca(tempUniCharBufferSize)) == NULL) { goto exitNow; } stackSize += tempUniCharBufferSize; } + else { if((tempUniCharBuffer = rkl_realloc(&scratchBuffer[0], tempUniCharBufferSize, 0UL)) == NULL) { goto exitNow; } } + + // Try to get the pointer to the replacement strings UTF16 data. If we can't, allocate some buffer space, then covert to UTF16. + if((replacementUniChar = CFStringGetCharactersPtr((CFStringRef)replacementString)) == NULL) { + if((stackSize + replacementSize) < RKL_STACK_LIMIT) { if((replacementUniChar = alloca(replacementSize)) == NULL) { goto exitNow; } stackSize += replacementSize; } + else { if((replacementUniChar = rkl_realloc(&scratchBuffer[1], replacementSize, 0UL)) == NULL) { goto exitNow; } } + CFStringGetCharacters((CFStringRef)replacementString, CFMakeRange(0, replacementU16Length), (UniChar *)replacementUniChar); // Convert to a UTF16 string. + } + + cacheSlot->lastFindRange = cacheSlot->lastMatchRange = NSNotFoundRange; // Clear the cached find information for this regex so a subsequent find works correctly. + + resultU16Length = rkl_replaceAll(cacheSlot, replacementUniChar, (int32_t)replacementU16Length, tempUniCharBuffer, tempUniCharBufferU16Capacity, &replacedCount, exception, status); + + if(*status == U_BUFFER_OVERFLOW_ERROR) { // Our buffer guess(es) were too small. Resize the buffers and try again. + tempUniCharBufferSize = ((tempUniCharBufferU16Capacity = resultU16Length + 4) * sizeof(UniChar)); + if((stackSize + tempUniCharBufferSize) < RKL_STACK_LIMIT) { if((tempUniCharBuffer = alloca(tempUniCharBufferSize)) == NULL) { goto exitNow; } stackSize += tempUniCharBufferSize; } + else { if((tempUniCharBuffer = rkl_realloc(&scratchBuffer[0], tempUniCharBufferSize, 0UL)) == NULL) { goto exitNow; } } + + *status = 0; // Make sure the status var is cleared and try again. + resultU16Length = rkl_replaceAll(cacheSlot, replacementUniChar, (int32_t)replacementU16Length, tempUniCharBuffer, tempUniCharBufferU16Capacity, &replacedCount, exception, status); + } + + if(*status > 0) { goto exitNow; } // Something went wrong. + + if(resultU16Length == 0) { resultObject = @""; } // Optimize the case where the replaced text length == 0 with a @"" string. + else if(((NSUInteger)resultU16Length == searchU16Length) && (replacedCount == 0)) { // Optimize the case where the replacement == original by creating a copy. Very fast if self is immutable. + if(replaceMutable == NO) { resultObject = rkl_CFAutorelease(CFStringCreateCopy(NULL, (CFStringRef)searchString)); } // .. but only if this is not replacing a mutable self. + } else { resultObject = rkl_CFAutorelease(CFStringCreateWithCharacters(NULL, tempUniCharBuffer, (CFIndex)resultU16Length)); } // otherwise, create a new string. + + // If replaceMutable == YES, we don't do the replacement here. We wait until after we return and unlock the cache lock. + // This is because we may be trying to mutate an immutable string object. + if((replacedCount > 0) && (replaceMutable == YES)) { // We're working on a mutable string and there were successfull matches with replaced text, so there's work to do. + clearBuffer((cacheSlot->setToLength < RKL_FIXED_LENGTH) ? &fixedBuffer : &dynamicBuffer, NO); + clearCacheSlotSetTo(cacheSlot); // Flush any cached information about this string since it will mutate. + } + + exitNow: + if(scratchBuffer[0] != NULL) { free(scratchBuffer[0]); scratchBuffer[0] = NULL; } + if(scratchBuffer[1] != NULL) { free(scratchBuffer[1]); scratchBuffer[1] = NULL; } + if(replacedCountPtr != NULL) { *replacedCountPtr = replacedCount; } + return(resultObject); +} + +// Modified version of the ICU libraries uregex_replaceAll() that keeps count of the number of replacements made. +static int32_t rkl_replaceAll(RKLCacheSlot *cacheSlot, const UniChar *replacementUniChar, int32_t replacementU16Length, UniChar *replacedUniChar, int32_t replacedU16Capacity, NSUInteger *replacedCount, id *exception, int32_t *status) { + NSUInteger replaced = 0; + int32_t u16Length = 0; + RKLCDelayedAssert((cacheSlot != NULL) && (replacementUniChar != NULL) && (replacedUniChar != NULL) && (status != NULL), exception, exitNow); + + uregex_reset(cacheSlot->icu_regex, 0, status); + + // Work around for ICU uregex_reset() bug, see http://bugs.icu-project.org/trac/ticket/6545 + // http://sourceforge.net/tracker/index.php?func=detail&aid=2105213&group_id=204582&atid=990188 + if((cacheSlot->setToLength == 0) && (*status == 8)) { *status = 0; } + + while(uregex_findNext(cacheSlot->icu_regex, status)) { + replaced++; + u16Length += uregex_appendReplacement(cacheSlot->icu_regex, replacementUniChar, replacementU16Length, &replacedUniChar, &replacedU16Capacity, status); + } + u16Length += uregex_appendTail(cacheSlot->icu_regex, &replacedUniChar, &replacedU16Capacity, status); + + if(replacedCount != 0) { *replacedCount = replaced; } + exitNow: + return(u16Length); +} + +static void rkl_clearStringCache(void) { + NSCParameterAssert(cacheSpinLock != 0); + lastCacheSlot = NULL; + NSUInteger x = 0; + for(x = 0; x < SCRATCH_BUFFERS; x++) { if(scratchBuffer[x] != NULL) { free(scratchBuffer[x]); scratchBuffer[x] = NULL; } } + for(x = 0; x < RKL_CACHE_SIZE; x++) { clearCacheSlotRegex(&RKLCache[x]); clearCacheSlotSetTo(&RKLCache[x]); } + clearBuffer(&fixedBuffer, NO); + clearBuffer(&dynamicBuffer, YES); +} + +static void clearBuffer(RKLBuffer *buffer, int freeDynamicBuffer) { + if(buffer == NULL) { return; } + if((freeDynamicBuffer == YES) && (buffer->uniChar != NULL) && (buffer == &dynamicBuffer)) { free(dynamicBuffer.uniChar); dynamicBuffer.uniChar = NULL; } + if(buffer->string != NULL) { CFRelease(buffer->string); buffer->string = NULL; } + buffer->length = 0L; + buffer->hash = 0UL; +} + +static void clearCacheSlotRegex(RKLCacheSlot *cacheSlot) { + if(cacheSlot == NULL) { return; } + if(cacheSlot->regexString != NULL) { CFRelease(cacheSlot->regexString); cacheSlot->regexString = NULL; cacheSlot->options = 0U; } + if(cacheSlot->icu_regex != NULL) { uregex_close(cacheSlot->icu_regex); cacheSlot->icu_regex = NULL; cacheSlot->captureCount = -1L; } + if(cacheSlot->setToString != NULL) { clearCacheSlotSetTo(cacheSlot); } +} + +static void clearCacheSlotSetTo(RKLCacheSlot *cacheSlot) { + if(cacheSlot == NULL) { return; } + if(cacheSlot->icu_regex != NULL) { int32_t status = 0; uregex_setText(cacheSlot->icu_regex, &emptyUniCharString[0], 0, &status); } + if(cacheSlot->setToString != NULL) { CFRelease(cacheSlot->setToString); cacheSlot->setToString = NULL; } + cacheSlot->setToLength = 0L; + cacheSlot->setToHash = 0UL; + cacheSlot->setToIsImmutable = cacheSlot->setToNeedsConversion = 0UL; + cacheSlot->lastFindRange = cacheSlot->lastMatchRange = cacheSlot->setToRange = NSNotFoundRange; + cacheSlot->setToUniChar = NULL; +} + +// Helps to keep things tidy. +#define addKeyAndObject(objs, keys, i, k, o) ({id _o=(o), _k=(k); if((_o != NULL) && (_k != NULL)) { objs[i] = _o; keys[i] = _k; i++; } }) + +static NSDictionary *userInfoDictionary(NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int status, ...) { + va_list varArgsList; + va_start(varArgsList, status); + + if(regexString == NULL) { return(NULL); } + + id objects[64], keys[64]; + NSUInteger count = 0; + + NSString *errorNameString = [NSString stringWithUTF8String:u_errorName(status)]; + + addKeyAndObject(objects, keys, count, RKLICURegexRegexErrorKey, regexString); + addKeyAndObject(objects, keys, count, RKLICURegexRegexOptionsErrorKey, [NSNumber numberWithUnsignedInt:options]); + addKeyAndObject(objects, keys, count, RKLICURegexErrorCodeErrorKey, [NSNumber numberWithInt:status]); + addKeyAndObject(objects, keys, count, RKLICURegexErrorNameErrorKey, errorNameString); + + if((parseError != NULL) && (parseError->line != -1)) { + NSString *preContextString = [NSString stringWithCharacters:&parseError->preContext[0] length:(NSUInteger)u_strlen(&parseError->preContext[0])]; + NSString *postContextString = [NSString stringWithCharacters:&parseError->postContext[0] length:(NSUInteger)u_strlen(&parseError->postContext[0])]; + + addKeyAndObject(objects, keys, count, RKLICURegexLineErrorKey, [NSNumber numberWithInt:parseError->line]); + addKeyAndObject(objects, keys, count, RKLICURegexOffsetErrorKey, [NSNumber numberWithInt:parseError->offset]); + addKeyAndObject(objects, keys, count, RKLICURegexPreContextErrorKey, preContextString); + addKeyAndObject(objects, keys, count, RKLICURegexPostContextErrorKey, postContextString); + addKeyAndObject(objects, keys, count, @"NSLocalizedFailureReason", ([NSString stringWithFormat:@"The error %@ occurred at line %d, column %d: %@<>%@", errorNameString, parseError->line, parseError->offset, preContextString, postContextString])); + } else { + addKeyAndObject(objects, keys, count, @"NSLocalizedFailureReason", ([NSString stringWithFormat:@"The error %@ occurred.", errorNameString])); + } + + while(count < 62) { id obj = va_arg(varArgsList, id), key = va_arg(varArgsList, id); if((obj != NULL) && (key != NULL)) { addKeyAndObject(objects, keys, count, key, obj); } else { break; } } + + return([NSDictionary dictionaryWithObjects:&objects[0] forKeys:&keys[0] count:count]); +} + +static NSError *RKLNSErrorForRegex(NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int status) { + return([NSError errorWithDomain:RKLICURegexErrorDomain code:(NSInteger)status userInfo:userInfoDictionary(regexString, options, parseError, status, @"There was an error compiling the regular expression.", @"NSLocalizedDescription", NULL)]); +} + +static NSException *RKLNSExceptionForRegex(NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int status) { + return([NSException exceptionWithName:RKLICURegexException reason:[NSString stringWithFormat:@"ICU regular expression error #%d, %s", status, u_errorName(status)] userInfo:userInfoDictionary(regexString, options, parseError, status, NULL)]); +} + +static NSDictionary *RKLCAssertDictionary(const char *function, const char *file, int line, NSString *format, ...) { + va_list varArgsList; + va_start(varArgsList, format); + NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; + va_end(varArgsList); + NSString *functionString = [NSString stringWithUTF8String:function], *fileString = [NSString stringWithUTF8String:file]; + return([NSDictionary dictionaryWithObjectsAndKeys:formatString, @"description", functionString, @"function", fileString, @"file", [NSNumber numberWithInt:line], @"line", NSInternalInconsistencyException, @"exceptionName", NULL]); +} + +static NSString *RKLStringFromClassAndMethod(id object, SEL selector, NSString *format, ...) { + va_list varArgsList; + va_start(varArgsList, format); + NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; + va_end(varArgsList); + Class objectsClass = [object class]; + return([NSString stringWithFormat:@"*** %c[%@ %@]: %@", (object == objectsClass) ? '+' : '-', NSStringFromClass(objectsClass), NSStringFromSelector(selector), formatString]); +} + +@implementation NSString (RegexKitLiteAdditions) + +// Class methods + ++ (void)RKL_METHOD_PREPEND(clearStringCache) +{ + OSSpinLockLock(&cacheSpinLock); + rkl_clearStringCache(); + OSSpinLockUnlock(&cacheSpinLock); +} + +// captureCountForRegex: + ++ (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex +{ + return([self RKL_METHOD_PREPEND(captureCountForRegex):regex options:RKLNoOptions error:NULL]); +} + ++ (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex options:(RKLRegexOptions)options error:(NSError **)error +{ + if((error != NULL) && (*error != NULL)) { *error = NULL; } + if(regex == NULL) { RKLRaiseException(NSInvalidArgumentException, @"The regular expression argument is NULL."); } + + NSException *exception = NULL; + RKLCacheSlot *cacheSlot = NULL; + NSInteger captureCount = -1; + + OSSpinLockLock(&cacheSpinLock); + if((cacheSlot = getCachedRegex(regex, options, error, &exception)) != NULL) { captureCount = cacheSlot->captureCount; } + OSSpinLockUnlock(&cacheSpinLock); + + if(exception != NULL) { [exception raise]; } + return(captureCount); +} + +// Instance methods + +// componentsSeparatedByRegex: + +- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex +{ + NSRange range = NSMaxiumRange; + return(performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, 0, 0L, self, &range, NULL, NULL, NULL)); +} + +- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex range:(NSRange)range +{ + return(performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, 0, 0L, self, &range, NULL, NULL, NULL)); +} + +- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error +{ + return(performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, options, 0L, self, &range, NULL, error, NULL)); +} + +// isMatchedByRegex: + +- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex +{ + NSRange result = NSNotFoundRange, range = NSMaxiumRange; + performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, 0, 0L, self, &range, NULL, NULL, (void **)((void *)&result)); + return((result.location == NSNotFound) ? NO : YES); +} + +- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex inRange:(NSRange)range +{ + NSRange result = NSNotFoundRange; + performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, 0, 0L, self, &range, NULL, NULL, (void **)((void *)&result)); + return((result.location == NSNotFound) ? NO : YES); +} + +- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error +{ + NSRange result = NSNotFoundRange; + performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, options, 0L, self, &range, NULL, error, (void **)((void *)&result)); + return((result.location == NSNotFound) ? NO : YES); +} + +// rangeOfRegex: + +- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex +{ + NSRange result = NSNotFoundRange, range = NSMaxiumRange; + performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, 0, 0L, self, &range, NULL, NULL, (void **)((void *)&result)); + return(result); +} + +- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex capture:(NSInteger)capture +{ + NSRange result = NSNotFoundRange, range = NSMaxiumRange; + performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, 0, capture, self, &range, NULL, NULL, (void **)((void *)&result)); + return(result); +} + +- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex inRange:(NSRange)range +{ + NSRange result = NSNotFoundRange; + performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, 0, 0L, self, &range, NULL, NULL, (void **)((void *)&result)); + return(result); +} + +- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error +{ + NSRange result = NSNotFoundRange; + performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, options, capture, self, &range, NULL, error, (void **)((void *)&result)); + return(result); +} + +// stringByMatching: + +- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex +{ + return([self RKL_METHOD_PREPEND(stringByMatching):regex options:RKLNoOptions inRange:NSMaxiumRange capture:0L error:NULL]); +} + +- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex capture:(NSInteger)capture +{ + return([self RKL_METHOD_PREPEND(stringByMatching):regex options:RKLNoOptions inRange:NSMaxiumRange capture:capture error:NULL]); +} + +- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex inRange:(NSRange)range +{ + return([self RKL_METHOD_PREPEND(stringByMatching):regex options:RKLNoOptions inRange:range capture:0L error:NULL]); +} + +- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error +{ + NSRange matchedRange = [self RKL_METHOD_PREPEND(rangeOfRegex):regex options:options inRange:range capture:capture error:error]; + return((matchedRange.location == NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length)))); +} + +// stringByReplacingOccurrencesOfRegex: + +- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement +{ + NSRange searchRange = NSMaxiumRange; + return(performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, 0, 0L, self, &searchRange, replacement, NULL, NULL)); +} + +- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange +{ + return(performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, 0, 0L, self, &searchRange, replacement, NULL, NULL)); +} + +- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error +{ + return(performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, options, 0L, self, &searchRange, replacement, error, NULL)); +} + +@end + + +@implementation NSMutableString (RegexKitLiteAdditions) + +// replaceOccurrencesOfRegex: + +- (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement +{ + NSRange searchRange = NSMaxiumRange; + NSUInteger replacedCount = 0; + performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, 0, 0L, self, &searchRange, replacement, NULL, (void **)((void *)&replacedCount)); + return(replacedCount); +} + +- (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange +{ + NSUInteger replacedCount = 0; + performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, 0, 0L, self, &searchRange, replacement, NULL, (void **)((void *)&replacedCount)); + return(replacedCount); +} + +- (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error +{ + NSUInteger replacedCount = 0; + performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, options, 0L, self, &searchRange, replacement, error, (void **)((void *)&replacedCount)); + return(replacedCount); +} + +@end + diff --git a/shared/ServerBlob.h b/shared/ServerBlob.h new file mode 100644 index 0000000..dd66fe1 --- /dev/null +++ b/shared/ServerBlob.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import +#import "InputStream.h" + +@interface ServerBlob : NSObject { + id is; + NSString *mimeType; + NSString *downloadName; +} +- (id)initWithInputStream:(id )theIS mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; +- (id)initWithData:(NSData *)data mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; +- (id )newInputStream; +- (NSString *)mimeType; +- (NSString *)downloadName; +- (NSData *)slurp:(NSError **)error; +@end diff --git a/shared/ServerBlob.m b/shared/ServerBlob.m new file mode 100644 index 0000000..0533a9f --- /dev/null +++ b/shared/ServerBlob.m @@ -0,0 +1,72 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#import "ServerBlob.h" +#import "DataInputStream.h" + +@implementation ServerBlob +- (id)initWithInputStream:(id )theIS mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { + if (self = [super init]) { + is = [theIS retain]; + mimeType = [theMimeType copy]; + downloadName = [theDownloadName copy]; + } + return self; +} +- (id)initWithData:(NSData *)data mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { + if (self = [super init]) { + is = [[DataInputStream alloc] initWithData:data]; + mimeType = [theMimeType copy]; + downloadName = [theDownloadName copy]; + } + return self; +} +- (void)dealloc { + [is release]; + [mimeType release]; + [downloadName release]; + [super dealloc]; +} +- (id )newInputStream { + return [is retain]; +} +- (NSString *)mimeType { + return [[mimeType retain] autorelease]; +} +- (NSString *)downloadName { + return [[downloadName retain] autorelease]; +} +- (NSData *)slurp:(NSError **)error { + NSData *data = [is slurp:error]; + return data; +} +@end diff --git a/shared/SetNSError.h b/shared/SetNSError.h new file mode 100644 index 0000000..e9b1ae7 --- /dev/null +++ b/shared/SetNSError.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + 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 + OWNER 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. + */ + +#define SETNSERROR(domain, theCode, format, args...) \ +if (error != NULL) {\ + *error = [NSError errorWithDomain:(domain) code:(theCode) userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:format, ##args], NSLocalizedDescriptionKey, nil]];\ + HSLogTrace(@"%@", [*error localizedDescription]);\ +} + +#define SETAMAZONNSERROR(theData, theStatusCode) \ +if (error != NULL) {\ + *error = [NSError errorFromAmazonXMLData:theData statusCode:theStatusCode];\ + HSLogDebug(@"%@", [*error localizedDescription]);\ +} + +#define LOGERROR \ +if (error != NULL) {\ + HSLogError(@"%@", *error); \ +}