From 4a346a274cd901d4645b51e6f0bce20bc5d3fcba Mon Sep 17 00:00:00 2001 From: Stefan Reitshamer Date: Wed, 31 Mar 2010 08:57:26 -0400 Subject: [PATCH] initial commit --- .gitignore | 1 + ArqFolder.h | 43 ++ ArqFolder.m | 55 ++ ArqRestoreCommand.h | 44 ++ ArqRestoreCommand.m | 191 +++++ ArqUserLibrary.h | 40 + ArqUserLibrary.m | 40 + Commit.h | 62 ++ Commit.m | 183 +++++ CommitFailedFile.h | 44 ++ CommitFailedFile.m | 65 ++ DiskPack.h | 53 ++ DiskPack.m | 232 ++++++ DiskPackIndex.h | 52 ++ DiskPackIndex.m | 300 ++++++++ FileAttributes.h | 91 +++ FileAttributes.m | 559 ++++++++++++++ HSLog.h | 53 ++ HSLog.m | 68 ++ Node.h | 104 +++ Node.m | 165 ++++ PackIndexEntry.h | 47 ++ PackIndexEntry.m | 63 ++ PackSet.h | 66 ++ PackSet.m | 200 +++++ PackSetSet.h | 54 ++ PackSetSet.m | 224 ++++++ RestoreNode.h | 48 ++ RestoreNode.m | 63 ++ Restorer.h | 48 ++ Restorer.m | 467 ++++++++++++ S3Fark.h | 53 ++ S3Fark.m | 109 +++ S3Repo.h | 83 ++ S3Repo.m | 291 +++++++ Tree.h | 92 +++ Tree.m | 156 ++++ XAttrSet.h | 45 ++ XAttrSet.m | 190 +++++ arq_restore.m | 69 ++ arq_restore.xcodeproj/.gitignore | 2 + arq_restore.xcodeproj/project.pbxproj | 825 ++++++++++++++++++++ arq_restore_Prefix.pch | 4 + arq_restore_usage | 8 + crypto/NSData-Base64Extensions.h | 28 + crypto/NSData-Base64Extensions.m | 57 ++ crypto/NSData-Encrypt.h | 41 + crypto/NSData-Encrypt.m | 190 +++++ crypto/OpenSSL.h | 43 ++ crypto/OpenSSL.m | 76 ++ crypto/SHA1Hash.h | 45 ++ crypto/SHA1Hash.m | 134 ++++ http/HTTP.h | 44 ++ http/HTTPConnection.h | 59 ++ http/HTTPConnection.m | 135 ++++ http/HTTPRequest.h | 61 ++ http/HTTPRequest.m | 128 ++++ http/HTTPResponse.h | 51 ++ http/HTTPResponse.m | 155 ++++ http/RFC2616DateFormatter.h | 41 + http/RFC2616DateFormatter.m | 60 ++ io/BooleanIO.h | 43 ++ io/BooleanIO.m | 55 ++ io/BufferedInputStream.h | 40 + io/CFStreamInputStream.h | 42 + io/CFStreamInputStream.m | 156 ++++ io/CFStreamOutputStream.h | 42 + io/CFStreamOutputStream.m | 86 +++ io/CFStreamPair.h | 51 ++ io/CFStreamPair.m | 235 ++++++ io/ChunkedInputStream.h | 43 ++ io/ChunkedInputStream.m | 92 +++ io/CryptInputStream.h | 58 ++ io/CryptInputStream.m | 187 +++++ io/DataIO.h | 42 + io/DataIO.m | 56 ++ io/DataInputStream.h | 42 + io/DataInputStream.m | 112 +++ io/DataInputStreamFactory.h | 40 + io/DataInputStreamFactory.m | 56 ++ io/DateIO.h | 41 + io/DateIO.m | 61 ++ io/DecryptedInputStream.h | 39 + io/DecryptedInputStream.m | 41 + io/DoubleIO.h | 41 + io/DoubleIO.m | 65 ++ io/EncryptedInputStream.h | 39 + io/EncryptedInputStream.m | 41 + io/FDInputStream.h | 42 + io/FDInputStream.m | 141 ++++ io/FDOutputStream.h | 42 + io/FDOutputStream.m | 64 ++ io/FileInputStream.h | 46 ++ io/FileInputStream.m | 162 ++++ io/FileInputStreamFactory.h | 41 + io/FileInputStreamFactory.m | 58 ++ io/FileOutputStream.h | 44 ++ io/FileOutputStream.m | 92 +++ io/FixedLengthInputStream.h | 43 ++ io/FixedLengthInputStream.m | 69 ++ io/InputStream.h | 40 + io/InputStreamFactory.h | 43 ++ io/InputStreams.h | 43 ++ io/InputStreams.m | 115 +++ io/IntegerIO.h | 58 ++ io/IntegerIO.m | 96 +++ io/NSData-InputStream.h | 38 + io/NSData-InputStream.m | 41 + io/NSFileManager_extra.h | 38 + io/NSFileManager_extra.m | 55 ++ io/OutputStream.h | 39 + io/StreamPair.h | 41 + io/StreamPairFactory.h | 46 ++ io/StreamPairFactory.m | 168 ++++ io/Streams.h | 42 + io/Streams.m | 91 +++ io/StringIO.h | 43 ++ io/StringIO.m | 78 ++ io/Writer.h | 41 + io/Writer.m | 52 ++ plist/ArrayNode.h | 58 ++ plist/ArrayNode.m | 106 +++ plist/BooleanNode.h | 41 + plist/BooleanNode.m | 61 ++ plist/DictNode.h | 73 ++ plist/DictNode.m | 188 +++++ plist/IntegerNode.h | 44 ++ plist/IntegerNode.m | 83 ++ plist/PListNode.h | 38 + plist/PListNodeType.h | 40 + plist/RealNode.h | 42 + plist/RealNode.m | 72 ++ plist/StringNode.h | 41 + plist/StringNode.m | 66 ++ plist/XMLPListReader.h | 41 + plist/XMLPListReader.m | 168 ++++ plist/XMLPListWriter.h | 41 + plist/XMLPListWriter.m | 163 ++++ s3/HTTPConnection_S3.h | 40 + s3/HTTPConnection_S3.m | 45 ++ s3/NSError_S3.h | 40 + s3/NSError_S3.m | 59 ++ s3/PathReceiver.h | 40 + s3/PathReceiver.m | 54 ++ s3/S3AuthorizationParameters.h | 56 ++ s3/S3AuthorizationParameters.m | 81 ++ s3/S3AuthorizationProvider.h | 44 ++ s3/S3AuthorizationProvider.m | 74 ++ s3/S3Lister.h | 52 ++ s3/S3Lister.m | 177 +++++ s3/S3ObjectMetadata.h | 46 ++ s3/S3ObjectMetadata.m | 96 +++ s3/S3ObjectReceiver.h | 40 + s3/S3ObjectReceiver.m | 55 ++ s3/S3Owner.h | 19 + s3/S3Owner.m | 26 + s3/S3Receiver.h | 38 + s3/S3Request.h | 51 ++ s3/S3Request.m | 236 ++++++ s3/S3Service.h | 69 ++ s3/S3Service.m | 238 ++++++ s3/S3Signature.h | 40 + s3/S3Signature.m | 85 +++ shared/BinarySHA1.h | 39 + shared/BinarySHA1.m | 50 ++ shared/Blob.h | 55 ++ shared/Blob.m | 112 +++ shared/BlobACL.h | 48 ++ shared/BlobACL.m | 79 ++ shared/DNS_SDErrors.h | 40 + shared/DNS_SDErrors.m | 101 +++ shared/FileACL.h | 40 + shared/FileACL.m | 85 +++ shared/NSErrorCodes.h | 37 + shared/NSString_extra.h | 39 + shared/NSString_extra.m | 90 +++ shared/NSXMLNode_extra.h | 38 + shared/NSXMLNode_extra.m | 52 ++ shared/OSStatusDescription.h | 40 + shared/OSStatusDescription.m | 55 ++ shared/RFC822.h | 40 + shared/RFC822.m | 54 ++ shared/RegexKitLite.h | 186 +++++ shared/RegexKitLite.m | 1013 +++++++++++++++++++++++++ shared/ServerBlob.h | 47 ++ shared/ServerBlob.m | 72 ++ shared/SetNSError.h | 48 ++ 187 files changed, 16525 insertions(+) create mode 100644 .gitignore create mode 100644 ArqFolder.h create mode 100644 ArqFolder.m create mode 100644 ArqRestoreCommand.h create mode 100644 ArqRestoreCommand.m create mode 100644 ArqUserLibrary.h create mode 100644 ArqUserLibrary.m create mode 100644 Commit.h create mode 100644 Commit.m create mode 100644 CommitFailedFile.h create mode 100644 CommitFailedFile.m create mode 100644 DiskPack.h create mode 100644 DiskPack.m create mode 100644 DiskPackIndex.h create mode 100644 DiskPackIndex.m create mode 100644 FileAttributes.h create mode 100644 FileAttributes.m create mode 100644 HSLog.h create mode 100644 HSLog.m create mode 100644 Node.h create mode 100644 Node.m create mode 100644 PackIndexEntry.h create mode 100644 PackIndexEntry.m create mode 100644 PackSet.h create mode 100644 PackSet.m create mode 100644 PackSetSet.h create mode 100644 PackSetSet.m create mode 100644 RestoreNode.h create mode 100644 RestoreNode.m create mode 100644 Restorer.h create mode 100644 Restorer.m create mode 100644 S3Fark.h create mode 100644 S3Fark.m create mode 100644 S3Repo.h create mode 100644 S3Repo.m create mode 100644 Tree.h create mode 100644 Tree.m create mode 100644 XAttrSet.h create mode 100644 XAttrSet.m create mode 100644 arq_restore.m create mode 100644 arq_restore.xcodeproj/.gitignore create mode 100644 arq_restore.xcodeproj/project.pbxproj create mode 100644 arq_restore_Prefix.pch create mode 100644 arq_restore_usage create mode 100644 crypto/NSData-Base64Extensions.h create mode 100644 crypto/NSData-Base64Extensions.m create mode 100644 crypto/NSData-Encrypt.h create mode 100644 crypto/NSData-Encrypt.m create mode 100644 crypto/OpenSSL.h create mode 100644 crypto/OpenSSL.m create mode 100644 crypto/SHA1Hash.h create mode 100644 crypto/SHA1Hash.m create mode 100644 http/HTTP.h create mode 100644 http/HTTPConnection.h create mode 100644 http/HTTPConnection.m create mode 100644 http/HTTPRequest.h create mode 100644 http/HTTPRequest.m create mode 100644 http/HTTPResponse.h create mode 100644 http/HTTPResponse.m create mode 100644 http/RFC2616DateFormatter.h create mode 100644 http/RFC2616DateFormatter.m create mode 100644 io/BooleanIO.h create mode 100644 io/BooleanIO.m create mode 100644 io/BufferedInputStream.h create mode 100644 io/CFStreamInputStream.h create mode 100644 io/CFStreamInputStream.m create mode 100644 io/CFStreamOutputStream.h create mode 100644 io/CFStreamOutputStream.m create mode 100644 io/CFStreamPair.h create mode 100644 io/CFStreamPair.m create mode 100644 io/ChunkedInputStream.h create mode 100644 io/ChunkedInputStream.m create mode 100644 io/CryptInputStream.h create mode 100644 io/CryptInputStream.m create mode 100644 io/DataIO.h create mode 100644 io/DataIO.m create mode 100644 io/DataInputStream.h create mode 100644 io/DataInputStream.m create mode 100644 io/DataInputStreamFactory.h create mode 100644 io/DataInputStreamFactory.m create mode 100644 io/DateIO.h create mode 100644 io/DateIO.m create mode 100644 io/DecryptedInputStream.h create mode 100644 io/DecryptedInputStream.m create mode 100644 io/DoubleIO.h create mode 100644 io/DoubleIO.m create mode 100644 io/EncryptedInputStream.h create mode 100644 io/EncryptedInputStream.m create mode 100644 io/FDInputStream.h create mode 100644 io/FDInputStream.m create mode 100644 io/FDOutputStream.h create mode 100644 io/FDOutputStream.m create mode 100644 io/FileInputStream.h create mode 100644 io/FileInputStream.m create mode 100644 io/FileInputStreamFactory.h create mode 100644 io/FileInputStreamFactory.m create mode 100644 io/FileOutputStream.h create mode 100644 io/FileOutputStream.m create mode 100644 io/FixedLengthInputStream.h create mode 100644 io/FixedLengthInputStream.m create mode 100644 io/InputStream.h create mode 100644 io/InputStreamFactory.h create mode 100644 io/InputStreams.h create mode 100644 io/InputStreams.m create mode 100644 io/IntegerIO.h create mode 100644 io/IntegerIO.m create mode 100644 io/NSData-InputStream.h create mode 100644 io/NSData-InputStream.m create mode 100644 io/NSFileManager_extra.h create mode 100644 io/NSFileManager_extra.m create mode 100644 io/OutputStream.h create mode 100644 io/StreamPair.h create mode 100644 io/StreamPairFactory.h create mode 100644 io/StreamPairFactory.m create mode 100644 io/Streams.h create mode 100644 io/Streams.m create mode 100644 io/StringIO.h create mode 100644 io/StringIO.m create mode 100644 io/Writer.h create mode 100644 io/Writer.m create mode 100644 plist/ArrayNode.h create mode 100644 plist/ArrayNode.m create mode 100644 plist/BooleanNode.h create mode 100644 plist/BooleanNode.m create mode 100644 plist/DictNode.h create mode 100644 plist/DictNode.m create mode 100644 plist/IntegerNode.h create mode 100644 plist/IntegerNode.m create mode 100644 plist/PListNode.h create mode 100644 plist/PListNodeType.h create mode 100644 plist/RealNode.h create mode 100644 plist/RealNode.m create mode 100644 plist/StringNode.h create mode 100644 plist/StringNode.m create mode 100644 plist/XMLPListReader.h create mode 100644 plist/XMLPListReader.m create mode 100644 plist/XMLPListWriter.h create mode 100644 plist/XMLPListWriter.m create mode 100644 s3/HTTPConnection_S3.h create mode 100644 s3/HTTPConnection_S3.m create mode 100644 s3/NSError_S3.h create mode 100644 s3/NSError_S3.m create mode 100644 s3/PathReceiver.h create mode 100644 s3/PathReceiver.m create mode 100644 s3/S3AuthorizationParameters.h create mode 100644 s3/S3AuthorizationParameters.m create mode 100644 s3/S3AuthorizationProvider.h create mode 100644 s3/S3AuthorizationProvider.m create mode 100644 s3/S3Lister.h create mode 100644 s3/S3Lister.m create mode 100644 s3/S3ObjectMetadata.h create mode 100644 s3/S3ObjectMetadata.m create mode 100644 s3/S3ObjectReceiver.h create mode 100644 s3/S3ObjectReceiver.m create mode 100644 s3/S3Owner.h create mode 100644 s3/S3Owner.m create mode 100644 s3/S3Receiver.h create mode 100644 s3/S3Request.h create mode 100644 s3/S3Request.m create mode 100644 s3/S3Service.h create mode 100644 s3/S3Service.m create mode 100644 s3/S3Signature.h create mode 100644 s3/S3Signature.m create mode 100644 shared/BinarySHA1.h create mode 100644 shared/BinarySHA1.m create mode 100644 shared/Blob.h create mode 100644 shared/Blob.m create mode 100644 shared/BlobACL.h create mode 100644 shared/BlobACL.m create mode 100644 shared/DNS_SDErrors.h create mode 100644 shared/DNS_SDErrors.m create mode 100644 shared/FileACL.h create mode 100644 shared/FileACL.m create mode 100644 shared/NSErrorCodes.h create mode 100644 shared/NSString_extra.h create mode 100644 shared/NSString_extra.m create mode 100644 shared/NSXMLNode_extra.h create mode 100644 shared/NSXMLNode_extra.m create mode 100644 shared/OSStatusDescription.h create mode 100644 shared/OSStatusDescription.m create mode 100644 shared/RFC822.h create mode 100644 shared/RFC822.m create mode 100644 shared/RegexKitLite.h create mode 100644 shared/RegexKitLite.m create mode 100644 shared/ServerBlob.h create mode 100644 shared/ServerBlob.m create mode 100644 shared/SetNSError.h 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); \ +}