Switched from NSURLConnection API to CFHTTP API.

I made this switch in Arq 2.6.3 last March, and it has been much more stable.
This commit is contained in:
Stefan Reitshamer 2012-08-20 10:00:49 -04:00
parent 0009121dcc
commit 40ef06c9d2
35 changed files with 2436 additions and 173 deletions

View file

@ -44,6 +44,7 @@
#import "Commit.h"
#import "S3ObjectMetadata.h"
#import "ArqSalt.h"
#import "S3Region.h"
@implementation BackupSet
+ (NSArray *)allBackupSetsForAccessKeyID:(NSString *)theAccessKeyID secretAccessKey:(NSString *)theSecretAccessKey error:(NSError **)error {
@ -125,7 +126,7 @@
#pragma mark NSObject
- (NSString *)description {
NSString *bucketRegion = [S3Service displayNameForBucketRegion:[S3Service s3BucketRegionForS3BucketName:s3BucketName]];
NSString *bucketRegion = [[S3Region s3RegionForBucketName:s3BucketName] displayName];
if (uac != nil) {
return [NSString stringWithFormat:@"%@ (%@) : %@ (%@)", [uac computerName], [uac userName], bucketRegion, computerUUID];
}

View file

@ -131,6 +131,28 @@
F83C1AD211CA7C170001958F /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8CD1160ECD7007EC01E /* CoreServices.framework */; };
F83C1AE311CA7C7C0001958F /* arq_restore.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* arq_restore.m */; };
F83C1D1D11CA95AF0001958F /* BucketVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = F83C1D0911CA929D0001958F /* BucketVerifier.m */; };
F84166D815E2782600B6ECED /* HTTPConnectionFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166D515E2782600B6ECED /* HTTPConnectionFactory.m */; };
F84166D915E2782600B6ECED /* HTTPTimeoutSetting.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166D715E2782600B6ECED /* HTTPTimeoutSetting.m */; };
F84166DA15E2782600B6ECED /* HTTPConnectionFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166D515E2782600B6ECED /* HTTPConnectionFactory.m */; };
F84166DB15E2782600B6ECED /* HTTPTimeoutSetting.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166D715E2782600B6ECED /* HTTPTimeoutSetting.m */; };
F84166E215E2785100B6ECED /* CFHTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166DF15E2785100B6ECED /* CFHTTPConnection.m */; };
F84166E315E2785100B6ECED /* CFHTTPInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166E115E2785100B6ECED /* CFHTTPInputStream.m */; };
F84166E415E2785100B6ECED /* CFHTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166DF15E2785100B6ECED /* CFHTTPConnection.m */; };
F84166E515E2785100B6ECED /* CFHTTPInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166E115E2785100B6ECED /* CFHTTPInputStream.m */; };
F84166ED15E278BC00B6ECED /* CFNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166EC15E278BC00B6ECED /* CFNetwork.m */; };
F84166EE15E278BC00B6ECED /* CFNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166EC15E278BC00B6ECED /* CFNetwork.m */; };
F84166F515E278F500B6ECED /* S3Owner.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166F215E278F500B6ECED /* S3Owner.m */; };
F84166F615E278F500B6ECED /* S3Region.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166F415E278F500B6ECED /* S3Region.m */; };
F84166F715E278F500B6ECED /* S3Owner.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166F215E278F500B6ECED /* S3Owner.m */; };
F84166F815E278F500B6ECED /* S3Region.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166F415E278F500B6ECED /* S3Region.m */; };
F84166FD15E2792500B6ECED /* NetMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166FC15E2792500B6ECED /* NetMonitor.m */; };
F84166FE15E2792500B6ECED /* NetMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166FC15E2792500B6ECED /* NetMonitor.m */; };
F841670515E279BB00B6ECED /* Sysctl.m in Sources */ = {isa = PBXBuildFile; fileRef = F841670415E279BB00B6ECED /* Sysctl.m */; };
F841670615E279BB00B6ECED /* Sysctl.m in Sources */ = {isa = PBXBuildFile; fileRef = F841670415E279BB00B6ECED /* Sysctl.m */; };
F841670B15E279DE00B6ECED /* DNS_SDErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = F841670A15E279DE00B6ECED /* DNS_SDErrors.m */; };
F841670C15E279DE00B6ECED /* DNS_SDErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = F841670A15E279DE00B6ECED /* DNS_SDErrors.m */; };
F841672615E27A5200B6ECED /* RemoteS3Signer.m in Sources */ = {isa = PBXBuildFile; fileRef = F841672515E27A5200B6ECED /* RemoteS3Signer.m */; };
F841672715E27A5200B6ECED /* RemoteS3Signer.m in Sources */ = {isa = PBXBuildFile; fileRef = F841672515E27A5200B6ECED /* RemoteS3Signer.m */; };
F8987235121EB68900F07D76 /* BinaryPListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = F8987232121EB68900F07D76 /* BinaryPListReader.m */; };
F8987236121EB68900F07D76 /* BinaryPListWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8987234121EB68900F07D76 /* BinaryPListWriter.m */; };
F8987560121EBD9600F07D76 /* BufferedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F898755F121EBD9600F07D76 /* BufferedInputStream.m */; };
@ -351,6 +373,29 @@
F83C1AD711CA7C170001958F /* arq_verify */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = arq_verify; sourceTree = BUILT_PRODUCTS_DIR; };
F83C1D0811CA929D0001958F /* BucketVerifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BucketVerifier.h; path = s3/BucketVerifier.h; sourceTree = "<group>"; };
F83C1D0911CA929D0001958F /* BucketVerifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BucketVerifier.m; path = s3/BucketVerifier.m; sourceTree = "<group>"; };
F84166D415E2782600B6ECED /* HTTPConnectionFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnectionFactory.h; sourceTree = "<group>"; };
F84166D515E2782600B6ECED /* HTTPConnectionFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPConnectionFactory.m; sourceTree = "<group>"; };
F84166D615E2782600B6ECED /* HTTPTimeoutSetting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPTimeoutSetting.h; sourceTree = "<group>"; };
F84166D715E2782600B6ECED /* HTTPTimeoutSetting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPTimeoutSetting.m; sourceTree = "<group>"; };
F84166DE15E2785100B6ECED /* CFHTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFHTTPConnection.h; sourceTree = "<group>"; };
F84166DF15E2785100B6ECED /* CFHTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFHTTPConnection.m; sourceTree = "<group>"; };
F84166E015E2785100B6ECED /* CFHTTPInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFHTTPInputStream.h; sourceTree = "<group>"; };
F84166E115E2785100B6ECED /* CFHTTPInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFHTTPInputStream.m; sourceTree = "<group>"; };
F84166E815E2787B00B6ECED /* HTTPConnectionDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnectionDelegate.h; sourceTree = "<group>"; };
F84166EB15E278BC00B6ECED /* CFNetwork.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFNetwork.h; sourceTree = "<group>"; };
F84166EC15E278BC00B6ECED /* CFNetwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFNetwork.m; sourceTree = "<group>"; };
F84166F115E278F500B6ECED /* S3Owner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Owner.h; sourceTree = "<group>"; };
F84166F215E278F500B6ECED /* S3Owner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Owner.m; sourceTree = "<group>"; };
F84166F315E278F500B6ECED /* S3Region.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Region.h; sourceTree = "<group>"; };
F84166F415E278F500B6ECED /* S3Region.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Region.m; sourceTree = "<group>"; };
F84166FB15E2792500B6ECED /* NetMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetMonitor.h; sourceTree = "<group>"; };
F84166FC15E2792500B6ECED /* NetMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetMonitor.m; sourceTree = "<group>"; };
F841670315E279BB00B6ECED /* Sysctl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Sysctl.h; sourceTree = "<group>"; };
F841670415E279BB00B6ECED /* Sysctl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Sysctl.m; sourceTree = "<group>"; };
F841670915E279DE00B6ECED /* DNS_SDErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS_SDErrors.h; sourceTree = "<group>"; };
F841670A15E279DE00B6ECED /* DNS_SDErrors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DNS_SDErrors.m; sourceTree = "<group>"; };
F841672415E27A5200B6ECED /* RemoteS3Signer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteS3Signer.h; sourceTree = "<group>"; };
F841672515E27A5200B6ECED /* RemoteS3Signer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteS3Signer.m; sourceTree = "<group>"; };
F8987231121EB68900F07D76 /* BinaryPListReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BinaryPListReader.h; sourceTree = "<group>"; };
F8987232121EB68900F07D76 /* BinaryPListReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BinaryPListReader.m; sourceTree = "<group>"; };
F8987233121EB68900F07D76 /* BinaryPListWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BinaryPListWriter.h; sourceTree = "<group>"; };
@ -622,6 +667,12 @@
F805B7651160DD60007EC01E /* s3 */ = {
isa = PBXGroup;
children = (
F841672415E27A5200B6ECED /* RemoteS3Signer.h */,
F841672515E27A5200B6ECED /* RemoteS3Signer.m */,
F84166F115E278F500B6ECED /* S3Owner.h */,
F84166F215E278F500B6ECED /* S3Owner.m */,
F84166F315E278F500B6ECED /* S3Region.h */,
F84166F415E278F500B6ECED /* S3Region.m */,
F89A205113FAE2DA0071D321 /* LocalS3Signer.h */,
F89A205213FAE2DA0071D321 /* LocalS3Signer.m */,
F805B76A1160DD60007EC01E /* NSError_S3.h */,
@ -649,6 +700,10 @@
F805B7A61160DEF2007EC01E /* shared */ = {
isa = PBXGroup;
children = (
F841670915E279DE00B6ECED /* DNS_SDErrors.h */,
F841670A15E279DE00B6ECED /* DNS_SDErrors.m */,
F841670315E279BB00B6ECED /* Sysctl.h */,
F841670415E279BB00B6ECED /* Sysctl.m */,
F89A1FD013FAD6BE0071D321 /* HSLog.h */,
F89A1FD113FAD6BE0071D321 /* HSLog.m */,
F89A1F4B13FAC73D0071D321 /* Computer.h */,
@ -690,6 +745,17 @@
F805B7C91160E445007EC01E /* http */ = {
isa = PBXGroup;
children = (
F84166EB15E278BC00B6ECED /* CFNetwork.h */,
F84166EC15E278BC00B6ECED /* CFNetwork.m */,
F84166E815E2787B00B6ECED /* HTTPConnectionDelegate.h */,
F84166DE15E2785100B6ECED /* CFHTTPConnection.h */,
F84166DF15E2785100B6ECED /* CFHTTPConnection.m */,
F84166E015E2785100B6ECED /* CFHTTPInputStream.h */,
F84166E115E2785100B6ECED /* CFHTTPInputStream.m */,
F84166D415E2782600B6ECED /* HTTPConnectionFactory.h */,
F84166D515E2782600B6ECED /* HTTPConnectionFactory.m */,
F84166D615E2782600B6ECED /* HTTPTimeoutSetting.h */,
F84166D715E2782600B6ECED /* HTTPTimeoutSetting.m */,
F89A1EEA13FAC4E30071D321 /* URLConnection.h */,
F89A1EEB13FAC4E30071D321 /* URLConnection.m */,
F805B7CA1160E445007EC01E /* HTTP.h */,
@ -703,6 +769,8 @@
F805B8081160E7A1007EC01E /* io */ = {
isa = PBXGroup;
children = (
F84166FB15E2792500B6ECED /* NetMonitor.h */,
F84166FC15E2792500B6ECED /* NetMonitor.m */,
F89A205713FAE3010071D321 /* DataOutputStream.h */,
F89A205813FAE3010071D321 /* DataOutputStream.m */,
F89A1F6113FAC8270071D321 /* CryptoKey.h */,
@ -995,6 +1063,17 @@
F81426D714541A6C00D7E50A /* BackupSet.m in Sources */,
F8373E0F14794D01005AFBE6 /* ReflogEntry.m in Sources */,
F8373E5A147A8DEC005AFBE6 /* ReflogPrinter.m in Sources */,
F84166D815E2782600B6ECED /* HTTPConnectionFactory.m in Sources */,
F84166D915E2782600B6ECED /* HTTPTimeoutSetting.m in Sources */,
F84166E215E2785100B6ECED /* CFHTTPConnection.m in Sources */,
F84166E315E2785100B6ECED /* CFHTTPInputStream.m in Sources */,
F84166ED15E278BC00B6ECED /* CFNetwork.m in Sources */,
F84166F515E278F500B6ECED /* S3Owner.m in Sources */,
F84166F615E278F500B6ECED /* S3Region.m in Sources */,
F84166FD15E2792500B6ECED /* NetMonitor.m in Sources */,
F841670515E279BB00B6ECED /* Sysctl.m in Sources */,
F841670B15E279DE00B6ECED /* DNS_SDErrors.m in Sources */,
F841672615E27A5200B6ECED /* RemoteS3Signer.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1099,6 +1178,17 @@
F89A205313FAE2DA0071D321 /* LocalS3Signer.m in Sources */,
F89A205913FAE3010071D321 /* DataOutputStream.m in Sources */,
F89A20CF13FAE7170071D321 /* NSError_extra.m in Sources */,
F84166DA15E2782600B6ECED /* HTTPConnectionFactory.m in Sources */,
F84166DB15E2782600B6ECED /* HTTPTimeoutSetting.m in Sources */,
F84166E415E2785100B6ECED /* CFHTTPConnection.m in Sources */,
F84166E515E2785100B6ECED /* CFHTTPInputStream.m in Sources */,
F84166EE15E278BC00B6ECED /* CFNetwork.m in Sources */,
F84166F715E278F500B6ECED /* S3Owner.m in Sources */,
F84166F815E278F500B6ECED /* S3Region.m in Sources */,
F84166FE15E2792500B6ECED /* NetMonitor.m in Sources */,
F841670615E279BB00B6ECED /* Sysctl.m in Sources */,
F841670C15E279DE00B6ECED /* DNS_SDErrors.m in Sources */,
F841672715E27A5200B6ECED /* RemoteS3Signer.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

83
http/CFHTTPConnection.h Normal file
View file

@ -0,0 +1,83 @@
/*
Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
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"
@protocol HTTPConnectionDelegate;
#import "InputStream.h"
@class RFC2616DateFormatter;
@class HTTPTimeoutSetting;
@interface CFHTTPConnection : NSObject <HTTPConnection, InputStream> {
RFC2616DateFormatter *dateFormatter;
NSURL *url;
NSString *requestMethod;
id <HTTPConnectionDelegate> httpConnectionDelegate;
NSMutableDictionary *requestHeaders;
CFHTTPMessageRef request;
NSInputStream *readStream;
BOOL errorOccurred;
NSError *_error;
BOOL complete;
BOOL hasBytesAvailable;
int responseStatusCode;
NSDictionary *responseHeaders;
NSTimeInterval createTime;
BOOL closeRequested;
CFHTTPConnection *previous;
NSDate *sendTimeout;
unsigned long long totalSent;
HTTPTimeoutSetting *httpTimeoutSetting;
}
+ (NSString *)errorDomain;
- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id <HTTPConnectionDelegate>)theDelegate;
- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id <HTTPConnectionDelegate>)theDelegate previousConnection:(CFHTTPConnection *)thePrevious;
- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key;
- (void)setRequestHostHeader;
- (void)setRequestContentDispositionHeader:(NSString *)downloadName;
- (void)setRFC822DateRequestHeader;
- (NSString *)requestMethod;
- (NSString *)requestPathInfo;
- (NSString *)requestQueryString;
- (NSArray *)requestHeaderKeys;
- (NSString *)requestHeaderForKey:(NSString *)theKey;
- (BOOL)executeRequest:(NSError **)error;
- (BOOL)executeRequestWithBody:(NSData *)requestBody error:(NSError **)error;
- (int)responseCode;
- (NSString *)responseHeaderForKey:(NSString *)key;
- (NSString *)responseContentType;
- (NSString *)responseDownloadName;
- (id <InputStream>)newResponseBodyStream:(NSError **)error;
- (void)setCloseRequested;
- (BOOL)isCloseRequested;
- (NSTimeInterval)createTime;
- (void)releasePreviousConnection;
@end

539
http/CFHTTPConnection.m Normal file
View file

@ -0,0 +1,539 @@
/*
Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "CFHTTPConnection.h"
#import "BufferedInputStream.h"
#import "HSLog.h"
#import "OutputStream.h"
#import "RFC2616DateFormatter.h"
#import "RegexKitLite.h"
#import "HTTP.h"
#import "SetNSError.h"
#import "ChunkedInputStream.h"
#import "FixedLengthInputStream.h"
#import "NSData-InputStream.h"
#import "DataInputStream.h"
#import "CFNetwork.h"
#import <SystemConfiguration/SystemConfiguration.h>
#import "CFNetwork.h"
#import "InputStreams.h"
#import "CFHTTPInputStream.h"
#import "NSErrorCodes.h"
#import "HTTPConnectionDelegate.h"
#import "HTTPTimeoutSetting.h"
#define DEFAULT_TIMEOUT_SECONDS (30.0)
#define MY_BUF_SIZE (8192)
static NSString *runLoopMode = @"HTTPConnectionRunLoopMode";
@interface CFHTTPConnection (usability)
- (BOOL)isUsable;
@end
@interface CFHTTPConnection (internal)
- (void)doExecuteRequestWithBody:(NSData *)requestBody;
- (void)setProxiesOnReadStream;
- (void)handleNetworkEvent:(CFStreamEventType)theType;
- (void)handleBytesAvailable;
- (void)handleStreamComplete;
- (void)handleStreamError;
- (void)readResponseHeaders;
- (void)destroyReadStream;
- (void)resetSendTimeout;
- (id <InputStream>)doNewResponseBodyStream:(NSError **)error;
- (NSInteger)doRead:(unsigned char *)buf bufferLength:(NSUInteger)length error:(NSError **)error;
@end
@interface CFHTTPConnection (callback)
- (void)sentRequestBytes:(NSInteger)count;
@end
static void ReadStreamClientCallback(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) {
[(CFHTTPConnection *)clientCallBackInfo handleNetworkEvent:type];
}
@implementation CFHTTPConnection
+ (NSString *)errorDomain {
return @"HTTPConnectionErrorDomain";
}
- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id <HTTPConnectionDelegate>)theDelegate {
return [self initWithURL:theURL method:theMethod httpTimeoutSetting:theHTTPTimeoutSetting httpConnectionDelegate:theDelegate previousConnection:nil];
}
- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id <HTTPConnectionDelegate>)theDelegate previousConnection:(CFHTTPConnection *)thePrevious {
if (self = [super init]) {
dateFormatter = [[RFC2616DateFormatter alloc] init];
url = [theURL retain];
requestMethod = [theMethod retain];
httpTimeoutSetting = [theHTTPTimeoutSetting retain];
httpConnectionDelegate = theDelegate;
requestHeaders = [[NSMutableDictionary alloc] init];
if (thePrevious != nil) {
previous = [thePrevious retain];
createTime = [thePrevious createTime];
} else {
createTime = [NSDate timeIntervalSinceReferenceDate];
}
}
return self;
}
- (void)dealloc {
[dateFormatter release];
[url release];
[requestMethod release];
[httpTimeoutSetting release];
[requestHeaders release];
if (request) {
CFRelease(request);
}
[runLoopMode release];
[responseHeaders release];
[self destroyReadStream];
[previous release];
[sendTimeout release];
[super dealloc];
}
- (NSString *)errorDomain {
return @"HTTPConnectionErrorDomain";
}
- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key {
[requestHeaders setObject:value forKey:key];
}
- (void)setRequestHostHeader {
[self setRequestHeader:[url host] forKey:@"Host"];
}
- (void)setRequestContentDispositionHeader:(NSString *)downloadName {
if (downloadName != nil) {
NSString *encodedFilename = [NSString stringWithFormat:@"\"%@\"", [downloadName stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\\\""]];
encodedFilename = [encodedFilename stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
NSString *contentDisposition = [NSString stringWithFormat:@"attachment;filename=%@", encodedFilename];
[self setRequestHeader:contentDisposition forKey:@"Content-Disposition"];
}
}
- (void)setRFC822DateRequestHeader {
[self setRequestHeader:[dateFormatter rfc2616StringFromDate:[NSDate date]] forKey:@"Date"];
}
- (NSString *)requestMethod {
return requestMethod;
}
//- (NSString *)requestPathInfo {
// return [[url path] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
//}
- (NSString *)requestPathInfo {
NSString *urlDescription = [url description];
NSRange rangeBeforeQueryString = [urlDescription rangeOfRegex:@"^([^?]+)"];
NSString *stringBeforeQueryString = [urlDescription substringWithRange:rangeBeforeQueryString];
NSString *path = [url path];
if ([stringBeforeQueryString hasSuffix:@"/"] && ![path hasSuffix:@"/"]) {
// NSURL's path method strips trailing slashes. Add it back in.
path = [path stringByAppendingString:@"/"];
}
return [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
- (NSString *)requestQueryString {
return [url query];
}
- (NSArray *)requestHeaderKeys {
return [requestHeaders allKeys];
}
- (NSString *)requestHeaderForKey:(NSString *)theKey {
return [requestHeaders objectForKey:theKey];
}
- (BOOL)executeRequest:(NSError **)error {
return [self executeRequestWithBody:nil error:error];
}
- (BOOL)executeRequestWithBody:(NSData *)requestBody error:(NSError **)error {
if (closeRequested) {
SETNSERROR([CFHTTPConnection errorDomain], -1, @"close was requested; can't reuse this connection");
return NO;
}
[self doExecuteRequestWithBody:requestBody];
if (errorOccurred) {
if (error != NULL) {
*error = _error;
}
if ([httpConnectionDelegate respondsToSelector:@selector(httpConnection:subtractSentBytes:)]) {
[httpConnectionDelegate httpConnection:self subtractSentBytes:totalSent];
}
return NO;
}
return YES;
}
- (int)responseCode {
if (responseHeaders == nil) {
// User probably canceled before we received the response header.
return HTTP_INTERNAL_SERVER_ERROR;
}
return responseStatusCode;
}
- (NSString *)responseHeaderForKey:(NSString *)key {
return [responseHeaders objectForKey:key];
}
- (NSString *)responseContentType {
return [self responseHeaderForKey:@"Content-Type"];
}
- (NSString *)responseDownloadName {
NSString *downloadName = nil;
NSString *contentDisposition = [self responseHeaderForKey:@"Content-Disposition"];
if (contentDisposition != nil) {
NSRange filenameRange = [contentDisposition rangeOfRegex:@"attachment;filename=(.+)" capture:1];
if (filenameRange.location != NSNotFound) {
downloadName = [contentDisposition substringWithRange:filenameRange];
}
}
return downloadName;
}
- (id <InputStream>)newResponseBodyStream:(NSError **)error {
id <InputStream> ret = [self doNewResponseBodyStream:error];
if (ret == nil && [httpConnectionDelegate respondsToSelector:@selector(httpConnection:subtractSentBytes:)]) {
[httpConnectionDelegate httpConnection:self subtractSentBytes:totalSent];
}
return ret;
}
- (void)setCloseRequested {
closeRequested = YES;
}
- (BOOL)isCloseRequested {
return closeRequested || errorOccurred;
}
- (NSTimeInterval)createTime {
return createTime;
}
- (void)releasePreviousConnection {
[previous release];
previous = nil;
}
#pragma mark InputStream
- (NSInteger)read:(unsigned char *)buf bufferLength:(NSUInteger)length error:(NSError **)error {
NSInteger ret = [self doRead:buf bufferLength:length error:error];
if (ret < 0 && [httpConnectionDelegate respondsToSelector:@selector(httpConnection:subtractSentBytes:)]) {
[httpConnectionDelegate httpConnection:self subtractSentBytes:totalSent];
}
return ret;
}
- (NSData *)slurp:(NSError **)error {
return [InputStreams slurp:self error:error];
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<HTTPConnection url=%@ method=%@>", url, requestMethod];
}
@end
@implementation CFHTTPConnection (internal)
- (void)doExecuteRequestWithBody:(NSData *)requestBody {
request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)requestMethod, (CFURLRef)url, kCFHTTPVersion1_1);
if (!request) {
errorOccurred = YES;
_error = [[NSError errorWithDomain:[CFNetwork errorDomain] code:-1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error creating request", NSLocalizedDescriptionKey, nil]] retain];
return;
}
// Add headers.
for (NSString *header in [requestHeaders allKeys]) {
CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[requestHeaders objectForKey:header]);
}
// Add keep-alive header every time:
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Keep-Alive"));
if ([requestBody length] > 0) {
CFHTTPInputStream *bodyStream = [[[CFHTTPInputStream alloc] initWithCFHTTPConnection:self data:requestBody httpConnectionDelegate:httpConnectionDelegate] autorelease];
readStream = (NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request, (CFReadStreamRef)bodyStream);
} else {
readStream = (NSInputStream *)CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
}
HSLogTrace(@"new readStream: %p", readStream);
if (!readStream) {
errorOccurred = YES;
_error = [[NSError errorWithDomain:[CFNetwork errorDomain] code:-1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error creating read stream", NSLocalizedDescriptionKey, nil]] retain];
return;
}
if ([[[url scheme] lowercaseString] isEqualToString:@"https"]) {
NSMutableDictionary *sslProperties = [NSDictionary dictionaryWithObjectsAndKeys:
(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel,
kCFBooleanTrue, kCFStreamSSLAllowsExpiredCertificates,
kCFBooleanTrue, kCFStreamSSLAllowsExpiredRoots,
kCFBooleanTrue, kCFStreamSSLAllowsAnyRoot,
kCFBooleanFalse, kCFStreamSSLValidatesCertificateChain,
kCFNull, kCFStreamSSLPeerName,
nil];
CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertySSLSettings, sslProperties);
}
[self setProxiesOnReadStream];
// Attempt to reuse this connection.
CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
CFStreamClientContext ctxt = { 0, self, NULL, NULL, NULL };
CFReadStreamSetClient((CFReadStreamRef)readStream, kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred, ReadStreamClientCallback, &ctxt);
[readStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:runLoopMode];
if (!CFReadStreamOpen((CFReadStreamRef)readStream)) {
errorOccurred = YES;
_error = [[NSError errorWithDomain:[CFNetwork errorDomain] code:-1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error opening read stream", NSLocalizedDescriptionKey, nil]] retain];
return;
}
[previous releasePreviousConnection];
}
- (void)setProxiesOnReadStream {
NSDictionary *proxySettings = (NSDictionary *)SCDynamicStoreCopyProxies(NULL);
NSArray *proxies = (NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)url, (CFDictionaryRef)proxySettings);
if ([proxies count] > 0) {
NSDictionary *proxy = [proxies objectAtIndex:0];
NSString *proxyType = [proxy objectForKey:(NSString *)kCFProxyTypeKey];
if (![proxyType isEqualToString:(NSString *)kCFProxyTypeNone]) {
NSString *proxyHost = [proxy objectForKey:(NSString *)kCFProxyHostNameKey];
int proxyPort = [[proxy objectForKey:(NSString *)kCFProxyPortNumberKey] intValue];
NSString *hostKey;
NSString *portKey;
if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost;
portKey = (NSString *)kCFStreamPropertySOCKSProxyPort;
} else {
hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost;
portKey = (NSString *)kCFStreamPropertyHTTPProxyPort;
if ([[[url scheme] lowercaseString] isEqualToString:@"https"]) {
hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost;
portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort;
}
}
// FIXME: Support proxy autconfiguration files (kCFProxyTypeAutoConfigurationURL) too!
NSDictionary *proxyToUse = [NSDictionary dictionaryWithObjectsAndKeys:
proxyHost, hostKey,
[NSNumber numberWithInt:proxyPort], portKey,
nil];
if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertySOCKSProxy, proxyToUse);
} else {
CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertyHTTPProxy, proxyToUse);
}
}
}
[proxies release];
[proxySettings release];
}
- (void)handleNetworkEvent:(CFStreamEventType)theType {
switch (theType) {
case kCFStreamEventHasBytesAvailable:
[self handleBytesAvailable];
break;
case kCFStreamEventEndEncountered:
[self handleStreamComplete];
break;
case kCFStreamEventErrorOccurred:
[self handleStreamError];
break;
default:
break;
}
}
- (void)handleBytesAvailable {
HSLogTrace(@"%@: handleBytesAvailable", self);
[self readResponseHeaders];
// Rarely, there aren't any data actually available.
if (!CFReadStreamHasBytesAvailable((CFReadStreamRef)readStream)) {
return;
}
hasBytesAvailable = YES;
}
- (void)handleStreamComplete {
HSLogTrace(@"%@: handleStreamComplete", self);
[self readResponseHeaders];
complete = YES;
}
- (void)handleStreamError {
HSLogTrace(@"%@: handleStreamError", self);
if (errorOccurred) {
NSLog(@"already have an error!");
return;
}
errorOccurred = YES;
_error = (NSError *)CFReadStreamCopyError((CFReadStreamRef)readStream);
complete = YES;
closeRequested = YES;
}
- (void)readResponseHeaders {
if (responseHeaders) {
return;
}
CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)readStream, kCFStreamPropertyHTTPResponseHeader);
if (message) {
if (!CFHTTPMessageIsHeaderComplete(message)) {
HSLogDebug(@"%@: header not complete!", self);
} else {
[responseHeaders release];
responseHeaders = (NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message);
responseStatusCode = (int)CFHTTPMessageGetResponseStatusCode(message);
}
CFRelease(message);
}
}
- (void)destroyReadStream {
if (readStream != nil) {
HSLogTrace(@"destroying readStream: %p", readStream);
CFReadStreamSetClient((CFReadStreamRef)readStream, kCFStreamEventNone, NULL, NULL);
[readStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:runLoopMode];
[readStream close];
[readStream release];
readStream = nil;
}
}
- (void)resetSendTimeout {
NSTimeInterval timeoutSeconds = [httpTimeoutSetting timeoutSeconds];
if (timeoutSeconds == 0) {
timeoutSeconds = DEFAULT_TIMEOUT_SECONDS;
}
[sendTimeout release];
sendTimeout = [[[NSDate date] addTimeInterval:timeoutSeconds] retain];
}
- (id <InputStream>)doNewResponseBodyStream:(NSError **)error {
while (!complete && !hasBytesAvailable) {
[self resetSendTimeout];
HSLogTrace(@"newResponseBodyStream: running the runloop until %f", [sendTimeout timeIntervalSinceReferenceDate]);
[[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:sendTimeout];
NSTimeInterval current = [NSDate timeIntervalSinceReferenceDate];
if ((current + 0.1) > [sendTimeout timeIntervalSinceReferenceDate]) {
HSLogWarn(@"timeout waiting for response to %@ %@", requestMethod, url);
SETNSERROR([self errorDomain], ERROR_TIMEOUT, @"timeout while attempting to send HTTP request");
closeRequested = YES;
return nil;
} else {
HSLogTrace(@"current time %f is not later than timeout %f; continuing", current, [sendTimeout timeIntervalSinceReferenceDate]);
}
}
if (errorOccurred) {
if ([httpConnectionDelegate abortRequestedForHTTPConnection:self]) {
[_error release];
_error = [[NSError alloc] initWithDomain:[self errorDomain] code:ERROR_ABORT_REQUESTED userInfo:[NSDictionary dictionaryWithObject:@"abort requested" forKey:NSLocalizedDescriptionKey]];
}
if (error != NULL) {
*error = _error;
}
return nil;
}
hasBytesAvailable = NO;
id <InputStream> ret = nil;
if (complete) {
HSLogTrace(@"%@: empty response body", self);
ret = [[NSData data] newInputStream];
} else {
NSAssert(responseHeaders != nil, @"responseHeaders can't be nil");
NSString *transferEncoding = [self responseHeaderForKey:@"Transfer-Encoding"];
// NSString *contentLength = [self responseHeaderForKey:@"Content-Length"];
HSLogTrace(@"Content-Length = %@", [self responseHeaderForKey:@"Content-Length"]);
HSLogTrace(@"Transfer-Encoding = %@", transferEncoding);
if (transferEncoding != nil && ![transferEncoding isEqualToString:@"Identity"]) {
if ([[transferEncoding lowercaseString] isEqualToString:@"chunked"]) {
HSLogTrace(@"%@: chunked response body", self);
ret = [[ChunkedInputStream alloc] initWithUnderlyingStream:self];
} else {
SETNSERROR(@"StreamErrorDomain", -1, @"unknown Transfer-Encoding '%@'", transferEncoding);
return nil;
}
// } else if (contentLength != nil) {
// int length = [contentLength intValue];
// BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:self];
// HSLogTrace(@"%@: fixed-length response body (%d bytes)", self, length);
//// ret = [[FixedLengthInputStream alloc] initWithUnderlyingStream:bis length:(NSUInteger)length];
//// [bis release];
// ret = bis;
} else {
/*
* FIXME: handle multipart/byteranges media type.
* See rfc2616 section 4.4 ("message length").
*/
HSLogTrace(@"%@: response body with no content-length", self);
ret = [self retain];
}
}
return ret;
}
- (NSInteger)doRead:(unsigned char *)buf bufferLength:(NSUInteger)length error:(NSError **)error {
NSInteger recvd = 0;
for (;;) {
NSTimeInterval timeoutSeconds = [httpTimeoutSetting timeoutSeconds];
if (timeoutSeconds == 0) {
timeoutSeconds = DEFAULT_TIMEOUT_SECONDS;
}
NSDate *timeout = [[NSDate date] addTimeInterval:timeoutSeconds];
while (!complete && !hasBytesAvailable) {
if ([timeout earlierDate:[NSDate date]] == timeout) {
HSLogWarn(@"timed out after %0.2f seconds waiting for response data from %@ %@", timeoutSeconds, requestMethod, url);
SETNSERROR([self errorDomain], ERROR_TIMEOUT, @"timeout after %0.2f seconds", timeoutSeconds);
closeRequested = YES;
return -1;
}
HSLogTrace(@"read: running the runloop until %@", timeout);
[[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:timeout];
}
hasBytesAvailable = NO;
if (errorOccurred) {
if (error != NULL) {
*error = _error;
}
return -1;
}
if (complete) {
return 0;
}
recvd = [readStream read:buf maxLength:length];
HSLogTrace(@"received %d bytes", recvd);
if (recvd < 0) {
[self handleStreamError];
if (error != NULL) {
*error = _error;
}
return -1;
}
if (recvd > 0) {
break;
}
}
return recvd;
}
@end
@implementation CFHTTPConnection (callback)
- (void)sentRequestBytes:(NSInteger)count {
totalSent += count;
}
@end

28
http/CFHTTPInputStream.h Normal file
View file

@ -0,0 +1,28 @@
//
// CFHTTPInputStream.h
// Arq
//
// Created by Stefan Reitshamer on 3/16/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class CFHTTPConnection;
@protocol HTTPConnectionDelegate;
@class NetMonitor;
@interface CFHTTPInputStream : NSObject {
CFHTTPConnection *conn;
NSInputStream *inputStream;
id <HTTPConnectionDelegate> httpConnectionDelegate;
int throttleType;
int throttleKBPS;
NSTimeInterval lastReceivedTime;
NSUInteger lastReceivedLength;
NSUInteger totalReceivedLength;
NetMonitor *netMonitor;
}
- (id)initWithCFHTTPConnection:(CFHTTPConnection *)theConn data:(NSData *)theData httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate;
@end

135
http/CFHTTPInputStream.m Normal file
View file

@ -0,0 +1,135 @@
//
// CFHTTPInputStream.m
// Arq
//
// Created by Stefan Reitshamer on 3/16/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
#import "CFHTTPInputStream.h"
#import "HTTPConnectionDelegate.h"
#import "CFHTTPConnection.h"
#import "NetMonitor.h"
@interface CFHTTPConnection (callback)
- (void)sentRequestBytes:(NSInteger)count;
@end
@implementation CFHTTPInputStream
- (id)initWithCFHTTPConnection:(CFHTTPConnection *)theConn data:(NSData *)theData httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate {
if (self = [super init]) {
conn = theConn; // Don't retain the connection.
inputStream = [[NSInputStream inputStreamWithData:theData] retain];
httpConnectionDelegate = theHTTPConnectionDelegate;
netMonitor = [[NetMonitor alloc] init];
}
return self;
}
- (void)dealloc {
[inputStream release];
[netMonitor release];
[super dealloc];
}
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
if (throttleType == THROTTLE_FIXED && throttleKBPS != 0) {
// Don't send more than 1/10th of the max bytes/sec:
NSUInteger maxLen = throttleKBPS * 100;
if (len > maxLen) {
len = maxLen;
}
if (lastReceivedTime != 0) {
NSTimeInterval interval = currentTime - lastReceivedTime;
// For some reason Activity Monitor reports "Data sent/sec" at twice what we seem to be sending!
// So we send half as much -- we divide by 500 instead of 1000 here:
NSTimeInterval throttledInterval = (double)lastReceivedLength / ((double)throttleKBPS * (double)500.0);
if (throttledInterval > interval) {
[NSThread sleepForTimeInterval:(throttledInterval - interval)];
}
}
}
if (throttleType == THROTTLE_AUTOMATIC) {
NSTimeInterval interval = currentTime - lastReceivedTime;
if (lastReceivedLength > 0) {
double myBPS = (double)lastReceivedLength / interval;
double throttle = [netMonitor sample:myBPS];
if (throttle < 1.0) {
HSLogDebug(@"throttle = %f", throttle);
}
NSTimeInterval throttledInterval = (throttle == 0) ? 0.5 : ((interval / throttle) - interval);
if (throttledInterval > 0) {
if (throttledInterval > 0.5) {
throttledInterval = 0.5;
}
HSLogDebug(@"auto-throttle: sleeping %f seconds", throttledInterval);
[NSThread sleepForTimeInterval:throttledInterval];
}
}
}
NSInteger ret = [inputStream read:buffer maxLength:len];
if (ret >= 0) {
if ([httpConnectionDelegate respondsToSelector:@selector(httpConnection:sentBytes:throttleType:throttleKBPS:pauseRequested:abortRequested:)]) {
BOOL pauseRequested = NO;
BOOL abortRequested = NO;
[httpConnectionDelegate httpConnection:conn sentBytes:ret throttleType:&throttleType throttleKBPS:&throttleKBPS pauseRequested:&pauseRequested abortRequested:&abortRequested];
if (pauseRequested || abortRequested) {
return -1;
}
}
lastReceivedTime = currentTime;
lastReceivedLength = ret;
totalReceivedLength += ret;
}
[conn sentRequestBytes:ret];
return ret;
}
// Implement most of the NSInputStream methods:
- (void)open {
[inputStream open];
}
- (void)close {
[inputStream close];
}
- (id)delegate {
return [inputStream delegate];
}
- (void)setDelegate:(id)theDelegate {
[inputStream setDelegate:theDelegate];
}
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode {
[inputStream scheduleInRunLoop:aRunLoop forMode:mode];
}
- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode {
[inputStream removeFromRunLoop:aRunLoop forMode:mode];
}
- (id)propertyForKey:(NSString *)key {
return [inputStream propertyForKey:key];
}
- (BOOL)setProperty:(id)property forKey:(NSString *)key {
return [inputStream setProperty:property forKey:key];
}
- (NSStreamStatus)streamStatus {
return [inputStream streamStatus];
}
- (NSError *)streamError {
return [inputStream streamError];
}
// Forward everything else to the inputStream ivar.
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [inputStream methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:inputStream];
}
@end

41
http/CFNetwork.h Normal file
View file

@ -0,0 +1,41 @@
/*
Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Cocoa/Cocoa.h>
@interface CFNetwork : NSObject {
}
+ (NSString *)errorDomain;
+ (NSError *)NSErrorWithNetworkError:(CFErrorRef)err;
@end

130
http/CFNetwork.m Normal file
View file

@ -0,0 +1,130 @@
/*
Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#import "CFNetwork.h"
#import "DNS_SDErrors.h"
@implementation CFNetwork
+ (NSString *)errorDomain {
return @"CFNetworkErrorDomain";
}
+ (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:[CFNetwork errorDomain] code:code userInfo:[NSDictionary dictionaryWithObjectsAndKeys:localizedDescription, NSLocalizedDescriptionKey, nil]];
}
@end

View file

@ -0,0 +1,19 @@
//
// HTTPConnectionDelegate.h
// Arq
//
// Created by Stefan Reitshamer on 3/16/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
@protocol HTTPConnection;
#define THROTTLE_NONE 0
#define THROTTLE_AUTOMATIC 1
#define THROTTLE_FIXED 2
@protocol HTTPConnectionDelegate <NSObject>
- (void)httpConnection:(id <HTTPConnection>)theHTTPConnection sentBytes:(unsigned long long)sent throttleType:(int *)theThrottleType throttleKBPS:(int *)theThrottleKBPS pauseRequested:(BOOL *)isPauseRequested abortRequested:(BOOL *)isAbortRequested;
- (void)httpConnection:(id <HTTPConnection>)theHTTPConnection subtractSentBytes:(unsigned long long)sent;
- (BOOL)abortRequestedForHTTPConnection:(id <HTTPConnection>)theHTTPConnection;
@end

View file

@ -0,0 +1,27 @@
//
// HTTPConnectionFactory.h
// Arq
//
// Created by Stefan Reitshamer on 3/15/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@protocol HTTPConnection;
@protocol HTTPConnectionDelegate;
@class HTTPTimeoutSetting;
@interface HTTPConnectionFactory : NSObject {
NSTimeInterval maxConnectionLifetime;
NSLock *lock;
NSMutableDictionary *connectionMapsByThreadId;
}
+ (HTTPConnectionFactory *)theFactory;
- (id <HTTPConnection>)newHTTPConnectionToURL:(NSURL *)theURL
method:(NSString *)theMethod
httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting
httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate;
@end

View file

@ -0,0 +1,163 @@
//
// HTTPConnectionFactory.m
// Arq
//
// Created by Stefan Reitshamer on 3/15/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
#import "HTTPConnectionFactory.h"
#import "CFHTTPConnection.h"
#import "URLConnection.h"
#define DEFAULT_MAX_HTTPCONNECTION_LIFETIME_SECONDS (20)
#define CLEANUP_THREAD_SLEEP_SECONDS (5)
@interface ConnectionMap : NSObject {
NSMutableDictionary *connections;
}
- (CFHTTPConnection *)newConnectionToURL:(NSURL *)theURL
method:(NSString *)theMethod
maxConnectionLifetime:(NSTimeInterval)theMaxConnectionLifetime
httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting
httpConnectionDelegate:(id <HTTPConnectionDelegate>)theDelegate;
- (void)dropUnusableConnections:(NSTimeInterval)theMaxConnectionLifetime;
@end
@implementation ConnectionMap
- (id)init {
if (self = [super init]) {
connections = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
[connections release];
[super dealloc];
}
- (CFHTTPConnection *)newConnectionToURL:(NSURL *)theURL
method:(NSString *)theMethod
maxConnectionLifetime:(NSTimeInterval)theMaxConnectionLifetime
httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting
httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate {
NSString *key = [NSString stringWithFormat:@"%@ %@://%@:%d", theMethod, [theURL scheme], [theURL host], [[theURL port] intValue]];
CFHTTPConnection *conn = [connections objectForKey:key];
if (conn != nil) {
if ([conn isCloseRequested] || (([NSDate timeIntervalSinceReferenceDate] - [conn createTime]) > theMaxConnectionLifetime)) {
[connections removeObjectForKey:key];
HSLogTrace(@"removing connection %p", conn);
conn = nil;
} else {
HSLogTrace(@"reusing connection %p", conn);
conn = [[CFHTTPConnection alloc] initWithURL:theURL method:theMethod httpTimeoutSetting:theHTTPTimeoutSetting httpConnectionDelegate:theHTTPConnectionDelegate previousConnection:conn];
[connections setObject:conn forKey:key];
}
}
if (conn == nil) {
HSLogTrace(@"new connection %p", conn);
conn = [[CFHTTPConnection alloc] initWithURL:theURL method:theMethod httpTimeoutSetting:theHTTPTimeoutSetting httpConnectionDelegate:theHTTPConnectionDelegate];
// [connections setObject:conn forKey:key];
}
return conn;
}
- (void)dropUnusableConnections:(NSTimeInterval)theMaxConnectionLifetime {
NSMutableArray *keysToDrop = [NSMutableArray array];
for (NSString *key in [connections allKeys]) {
CFHTTPConnection *conn = [connections objectForKey:key];
if ([conn isCloseRequested] || (([NSDate timeIntervalSinceReferenceDate] - [conn createTime]) > theMaxConnectionLifetime)) { // FIXME: Duplicate logic to newConnectionToURL: method
[keysToDrop addObject:key];
}
}
if ([keysToDrop count] > 0) {
HSLogTrace(@"dropping %@", keysToDrop);
[connections removeObjectsForKeys:keysToDrop];
}
}
@end
static HTTPConnectionFactory *theFactory = nil;
@implementation HTTPConnectionFactory
+ (HTTPConnectionFactory *)theFactory {
if (theFactory == nil) {
theFactory = [[super allocWithZone:NULL] init];
}
return theFactory;
}
/* Singleton recipe: */
+ (id)allocWithZone:(NSZone *)zone {
return [[HTTPConnectionFactory 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:@"HTTPConnectionFactory lock"];
connectionMapsByThreadId = [[NSMutableDictionary alloc] init];
maxConnectionLifetime = DEFAULT_MAX_HTTPCONNECTION_LIFETIME_SECONDS;
[NSThread detachNewThreadSelector:@selector(dropUnusableConnections) toTarget:self withObject:nil];
}
return self;
}
- (void)dealloc {
[lock release];
[connectionMapsByThreadId release];
[super dealloc];
}
- (id <HTTPConnection>)newHTTPConnectionToURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate {
id <HTTPConnection> ret = nil;
void *pthreadPtr = pthread_self();
#ifdef __LP64__
NSNumber *threadID = [NSNumber numberWithUnsignedLongLong:(uint64_t)pthreadPtr];
#else
NSNumber *threadID = [NSNumber numberWithUnsignedLong:(uint32_t)pthreadPtr];
#endif
[lock lock];
ConnectionMap *connMap = [connectionMapsByThreadId objectForKey:threadID];
if (connMap == nil) {
connMap = [[ConnectionMap alloc] init];
[connectionMapsByThreadId setObject:connMap forKey:threadID];
[connMap release];
}
ret = [connMap newConnectionToURL:theURL method:theMethod maxConnectionLifetime:maxConnectionLifetime httpTimeoutSetting:theHTTPTimeoutSetting httpConnectionDelegate:theHTTPConnectionDelegate];
[lock unlock];
return ret;
}
#pragma mark cleanup thread
- (void)dropUnusableConnections {
[self retain];
for (;;) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread sleepForTimeInterval:CLEANUP_THREAD_SLEEP_SECONDS];
[lock lock];
for (ConnectionMap *connMap in [connectionMapsByThreadId allValues]) {
@try {
[connMap dropUnusableConnections:maxConnectionLifetime];
} @catch(NSException *e) {
HSLogError(@"unexpected exception in HTTPConnectionFactory cleanup thread: %@", [e description]);
}
}
[lock unlock];
[pool drain];
}
}
@end

16
http/HTTPTimeoutSetting.h Normal file
View file

@ -0,0 +1,16 @@
//
// HTTPTimeoutSetting.h
// Arq
//
// Created by Stefan Reitshamer on 4/6/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
@interface HTTPTimeoutSetting : NSObject {
NSTimeInterval timeoutSeconds;
}
- (id)init;
- (id)initWithTimeoutSeconds:(NSTimeInterval)theTimeoutSeconds;
- (NSTimeInterval)timeoutSeconds;
@end

35
http/HTTPTimeoutSetting.m Normal file
View file

@ -0,0 +1,35 @@
//
// HTTPTimeoutSetting.m
// Arq
//
// Created by Stefan Reitshamer on 4/6/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
#import "HTTPTimeoutSetting.h"
#define DEFAULT_TIMEOUT_SECONDS 30.0
@implementation HTTPTimeoutSetting
- (id)init {
self = [super init];
return self;
}
- (id)initWithTimeoutSeconds:(NSTimeInterval)theTimeoutSeconds {
if (self = [super init]) {
timeoutSeconds = theTimeoutSeconds;
}
return self;
}
- (NSTimeInterval)timeoutSeconds {
NSTimeInterval ret = timeoutSeconds;
if (ret == 0) {
[[NSUserDefaults standardUserDefaults] synchronize];
ret = (NSTimeInterval)[[NSUserDefaults standardUserDefaults] doubleForKey:@"HTTPTimeoutSeconds"];
}
if (ret == 0) {
ret = DEFAULT_TIMEOUT_SECONDS;
}
return ret;
}
@end

48
io/NetMonitor.h Normal file
View file

@ -0,0 +1,48 @@
/*
Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Cocoa/Cocoa.h>
#import <Cocoa/Cocoa.h>
@class NetMonSample;
@interface NetMonitor : NSObject {
NetMonSample *currentSample;
NetMonSample *previousSample;
NSTimeInterval overThresholdTime;
NSUInteger numBPSSamples;
double bpsSamples[4];
}
- (id)init;
- (double)sample:(double)myBPS;
- (double)averageBPS;
@end

132
io/NetMonitor.m Normal file
View file

@ -0,0 +1,132 @@
/*
Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Cocoa/Cocoa.h>
#import "NetMonitor.h"
#import "Sysctl.h"
#define OVERHEAD_IN_BYTES (300)
#define THROTTLE_UP_SECONDS (8.0)
#define THRESHOLD_RATIO (.3)
@interface NetMonSample : NSObject {
NSTimeInterval timeInterval;
unsigned long long rawBytesIn;
}
@property NSTimeInterval timeInterval;
@property unsigned long long rawBytesIn;
@end
@implementation NetMonSample
@synthesize timeInterval, rawBytesIn;
@end
@implementation NetMonitor
- (id)init {
if (self = [super init]) {
currentSample = [[NetMonSample alloc] init];
previousSample = [[NetMonSample alloc] init];
NSError *error = nil;
unsigned long long rawinb;
unsigned long long rawoutb;
if (![Sysctl networkBytesIn:&rawinb bytesOut:&rawoutb error:&error]) {
HSLogError(@"failed to get net stats: %@", [error localizedDescription]);
[self release];
return nil;
}
currentSample.timeInterval = [NSDate timeIntervalSinceReferenceDate];
currentSample.rawBytesIn = rawinb;
overThresholdTime = [[NSDate distantPast] timeIntervalSinceReferenceDate];
memset(bpsSamples, 0, sizeof(bpsSamples));
}
return self;
}
- (void)dealloc {
[currentSample release];
[previousSample release];
[super dealloc];
}
- (double)sample:(double)myBPS {
bpsSamples[(numBPSSamples++ % 4)] = myBPS;
double throttle = 1.0;
if (numBPSSamples > 1) {
unsigned long long rawinb;
unsigned long long rawoutb;
NSError *error = nil;
if (![Sysctl networkBytesIn:&rawinb bytesOut:&rawoutb error:&error]) {
HSLogError(@"failed to get net stats: %@", [error localizedDescription]);
} else {
previousSample.timeInterval = currentSample.timeInterval;
previousSample.rawBytesIn = currentSample.rawBytesIn;
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
currentSample.timeInterval = now;
currentSample.rawBytesIn = rawinb;
// I got a crash once because rawinb was smaller than previousSample.rawBytesIn, so now we check:
if (rawinb >= previousSample.rawBytesIn) {
if (previousSample.timeInterval != 0) {
unsigned long long bytesIn = currentSample.rawBytesIn - previousSample.rawBytesIn;
if (bytesIn > OVERHEAD_IN_BYTES) {
bytesIn -= OVERHEAD_IN_BYTES;
}
NSTimeInterval interval = currentSample.timeInterval - previousSample.timeInterval;
double inBPS = (double)bytesIn / interval;
double ratio = myBPS == 0 ? 0 : (inBPS / myBPS);
BOOL overThreshold = myBPS != 0 && ratio > THRESHOLD_RATIO;
HSLogTrace(@"sent: %0.2fBPS bytesIn: %qu (%0.2fBPS) ratio: %0.2f threshold: %0.2f interval: %0.2f %@", myBPS, bytesIn, inBPS, ratio, THRESHOLD_RATIO, interval, (overThreshold ? @": over threshold" : @""));
if (overThreshold) {
overThresholdTime = [NSDate timeIntervalSinceReferenceDate];
}
}
}
double secsSinceOverage = now - overThresholdTime;
if (secsSinceOverage < THROTTLE_UP_SECONDS) {
throttle = secsSinceOverage / THROTTLE_UP_SECONDS;
if (throttle < 0.0001) {
throttle = 0.0001;
}
HSLogTrace(@"secsSinceOverage=%f throttle=%0.2f", secsSinceOverage, throttle);
}
}
}
return throttle;
}
- (double)averageBPS {
NSUInteger num = numBPSSamples > 4 ? 4 : numBPSSamples;
double total = 0.0;
for (NSUInteger i = 0; i < num; i++) {
total += bpsSamples[i];
}
return total / (double)num;
}
@end

20
s3/RemoteS3Signer.h Normal file
View file

@ -0,0 +1,20 @@
//
// RemoteS3Signer.h
// Arq
//
// Created by Stefan Reitshamer on 12/30/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "S3Signer.h"
@interface RemoteS3Signer : NSObject <S3Signer> {
NSString *accessKey;
NSURL *url;
NSString *account;
NSString *password;
}
+ (NSString *)errorDomain;
- (id)initWithAccessKey:(NSString *)theAccessKey url:(NSURL *)theURL account:(NSString *)theAccount password:(NSString *)thePassword;
@end

75
s3/RemoteS3Signer.m Normal file
View file

@ -0,0 +1,75 @@
//
// RemoteS3Signer.m
// Arq
//
// Created by Stefan Reitshamer on 12/30/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "RemoteS3Signer.h"
#import "HTTPConnection.h"
#import "URLConnection.h"
#import "NSData-InputStream.h"
#import "SetNSError.h"
#import "HTTP.h"
#import "InputStream.h"
@implementation RemoteS3Signer
+ (NSString *)errorDomain {
return @"RemoteS3SignerErrorDomain";
}
- (id)initWithAccessKey:(NSString *)theAccessKey url:(NSURL *)theURL account:(NSString *)theAccount password:(NSString *)thePassword {
if (self = [super init]) {
accessKey = [theAccessKey retain];
url = [theURL retain];
account = [theAccount retain];
password = [thePassword retain];
}
return self;
}
- (void)dealloc {
[accessKey release];
[url release];
[account release];
[password release];
[super dealloc];
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
return [[RemoteS3Signer alloc] initWithAccessKey:accessKey url:url account:account password:password];
}
#pragma mark S3Signer
- (NSString *)sign:(NSString *)theString error:(NSError **)error {
id <HTTPConnection> conn = [[[URLConnection alloc] initWithURL:url method:@"POST" delegate:nil] autorelease];
[conn setRequestHeader:accessKey forKey:@"X-Arq-AccessKey"];
NSData *requestData = [theString dataUsingEncoding:NSUTF8StringEncoding];
if (![conn executeRequestWithBody:requestData error:error]) {
return nil;
}
id <InputStream> responseBodyStream = [conn newResponseBodyStream:error];
if (responseBodyStream == nil) {
return nil;
}
NSData *data = [responseBodyStream slurp:error];
[responseBodyStream release];
if (data == nil) {
return nil;
}
int code = [conn responseCode];
if (code != HTTP_OK) {
SETNSERROR([RemoteS3Signer errorDomain], -1, @"unexpected HTTP status code %d", code);
return nil;
}
NSString *sig = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease];
while ([sig hasSuffix:@"\n"]) {
sig = [sig substringToIndex:([sig length] - 1)];
}
HSLogTrace(@"signature: %@", sig);
return sig;
}
@end

View file

@ -34,11 +34,13 @@
@protocol S3Signer;
@protocol HTTPConnection;
@interface S3AuthorizationProvider : NSObject {
@interface S3AuthorizationProvider : NSObject <NSCopying> {
NSString *accessKey;
id <S3Signer> signer;
}
- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret;
- (id)initWithAccessKey:(NSString *)access url:(NSURL *)theURL account:(NSString *)theAccount password:(NSString *)thePassword;
- (id)initWithAccessKey:(NSString *)access signer:(id <S3Signer>)theSigner;
- (NSString *)accessKey;
- (BOOL)setAuthorizationRequestHeaderOnHTTPConnection:(id <HTTPConnection>)conn usingS3BucketName:(NSString *)s3BucketName error:(NSError **)error;
- (BOOL)setAuthorizationRequestHeaderOnHTTPConnection:(id <HTTPConnection>)conn error:(NSError **)error;
@end

View file

@ -32,6 +32,7 @@
#import "S3AuthorizationProvider.h"
#import "LocalS3Signer.h"
#import "RemoteS3Signer.h"
#import "HTTPConnection.h"
/*
@ -41,7 +42,7 @@
@interface S3AuthorizationProvider (internal)
- (NSString *)authorizationForString:(NSString *)stringToSign error:(NSError **)error;
- (NSString *)stringToSignForS3BucketName:(NSString *)theS3BucketName connection:(id <HTTPConnection>)theConnection;
- (NSString *)stringToSignForConnection:(id <HTTPConnection>)theConnection;
@end
@implementation S3AuthorizationProvider
@ -54,6 +55,20 @@
}
return self;
}
- (id)initWithAccessKey:(NSString *)access url:(NSURL *)theURL account:(NSString *)theAccount password:(NSString *)thePassword {
if (self = [super init]) {
accessKey = [access copy];
signer = [[RemoteS3Signer alloc] initWithAccessKey:accessKey url:theURL account:theAccount password:thePassword];
}
return self;
}
- (id)initWithAccessKey:(NSString *)access signer:(id <S3Signer>)theSigner {
if (self = [super init]) {
accessKey = [access retain];
signer = [theSigner retain];
}
return self;
}
- (void)dealloc {
[accessKey release];
[signer release];
@ -62,8 +77,8 @@
- (NSString *)accessKey {
return accessKey;
}
- (BOOL)setAuthorizationRequestHeaderOnHTTPConnection:(id <HTTPConnection>)conn usingS3BucketName:(NSString *)s3BucketName error:(NSError **)error {
NSString *stringToSign = [self stringToSignForS3BucketName:s3BucketName connection:conn];
- (BOOL)setAuthorizationRequestHeaderOnHTTPConnection:(id <HTTPConnection>)conn error:(NSError **)error {
NSString *stringToSign = [self stringToSignForConnection:conn];
NSString *authorization = [self authorizationForString:stringToSign error:error];
if (authorization == nil) {
return NO;
@ -71,6 +86,12 @@
[conn setRequestHeader:authorization forKey:@"Authorization"];
return YES;
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
return [[S3AuthorizationProvider alloc] initWithAccessKey:accessKey signer:signer];
}
@end
@implementation S3AuthorizationProvider (internal)
@ -91,7 +112,7 @@
}
return ret;
}
- (NSString *)stringToSignForS3BucketName:(NSString *)theS3BucketName connection:(id <HTTPConnection>)theConnection {
- (NSString *)stringToSignForConnection:(id <HTTPConnection>)theConnection {
NSMutableString *buf = [[[NSMutableString alloc] init] autorelease];
[buf appendString:[theConnection requestMethod]];
[buf appendString:@"\n"];
@ -118,10 +139,6 @@
for (NSString *xamz in xamzHeaders) {
[buf appendString:xamz];
}
if ([theS3BucketName length] > 0) {
[buf appendString:@"/"];
[buf appendString:theS3BucketName];
}
[buf appendString:[theConnection requestPathInfo]];
NSString *queryString = [theConnection requestQueryString];
if ([queryString isEqualToString:@"?acl"]

View file

@ -125,7 +125,10 @@
[queryString appendString:[NSString stringWithFormat:@"&marker=%@", [suffix stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
}
S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:[NSString stringWithFormat:@"/%@/", s3BucketName] queryString:queryString authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError];
S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:[NSString stringWithFormat:@"/%@/", s3BucketName] queryString:queryString authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error];
if (s3r == nil) {
return NO;
}
ServerBlob *sb = [s3r newServerBlob:error];
[s3r release];
if (sb == nil) {
@ -136,8 +139,10 @@
if (data == nil) {
return NO;
}
NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:data options:0 error:error] autorelease];
NSError *myError = nil;
NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:data options:0 error:&myError] autorelease];
if (!xmlDoc) {
SETNSERROR([S3Service errorDomain], [myError code], @"error parsing List Objects XML response: %@", myError);
return NO;
}
NSString *lastPath = nil;

43
s3/S3Owner.h Normal file
View file

@ -0,0 +1,43 @@
/*
Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Cocoa/Cocoa.h>
@interface S3Owner : NSObject {
NSString *displayName;
NSString *idString;
}
- (id)initWithDisplayName:(NSString *)dn idString:(NSString *)ids;
- (NSString *)displayName;
- (NSString *)idString;
@end

50
s3/S3Owner.m Normal file
View file

@ -0,0 +1,50 @@
/*
Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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

39
s3/S3Region.h Normal file
View file

@ -0,0 +1,39 @@
//
// S3Region.h
// Arq
//
// Created by Stefan Reitshamer on 2/11/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface S3Region : NSObject {
NSString *bucketNameSuffix;
NSString *legacyBucketNameSuffix;
NSString *locationConstraint;
NSString *endpoint;
NSString *displayName;
double dollarsPerGBMonthStandard;
double dollarsPerGBMonthRRS;
}
+ (NSArray *)allS3Regions;
+ (S3Region *)s3RegionForBucketName:(NSString *)theBucketName;
+ (S3Region *)usStandard;
+ (S3Region *)usWestNorthernCalifornia;
+ (S3Region *)usWestOregon;
+ (S3Region *)euIreland;
+ (S3Region *)asiaPacificSingapore;
+ (S3Region *)asiaPacificTokyo;
+ (S3Region *)southAmericaSaoPaulo;
- (NSString *)bucketNameSuffix;
- (NSString *)legacyBucketNameSuffix;
- (NSString *)locationConstraint;
- (NSString *)endpoint;
- (NSString *)displayName;
- (double)dollarsPerGBMonthStandard;
- (double)dollarsPerGBMonthRRS;
- (NSString *)bucketNameForAccessKeyID:(NSString *)theAccessKeyID;
@end

162
s3/S3Region.m Normal file
View file

@ -0,0 +1,162 @@
//
// S3Region.m
// Arq
//
// Created by Stefan Reitshamer on 2/11/12.
// Copyright 2012 __MyCompanyName__. All rights reserved.
//
#import "S3Region.h"
@interface S3Region (internal)
- (id)initWithBucketNameSuffix:(NSString *)theBucketNameSuffix
legacyBucketNameSuffix:(NSString *)theLegacyBucketNameSuffix
locationConstraint:(NSString *)theLocationConstraint
endpoint:(NSString *)theEndpoint
displayName:(NSString *)theDisplayName
dollarsPerGBMonthStandard:(double)theDollarsPerGBMonthStandard
dollarsPerGBMonthRRS:(double)theDollarsPerGBMonthRRS;
@end
@implementation S3Region
+ (NSArray *)allS3Regions {
NSMutableArray *ret = [NSMutableArray array];
[ret addObject:[S3Region usStandard]];
[ret addObject:[S3Region usWestNorthernCalifornia]];
[ret addObject:[S3Region usWestOregon]];
[ret addObject:[S3Region euIreland]];
[ret addObject:[S3Region asiaPacificSingapore]];
[ret addObject:[S3Region asiaPacificTokyo]];
[ret addObject:[S3Region southAmericaSaoPaulo]];
return ret;
}
+ (S3Region *)s3RegionForBucketName:(NSString *)theBucketName {
for (S3Region *region in [S3Region allS3Regions]) {
if ([[region bucketNameSuffix] length] > 0 && ([theBucketName hasSuffix:[region bucketNameSuffix]] || [theBucketName hasSuffix:[region legacyBucketNameSuffix]])) {
return region;
}
}
return [S3Region usStandard];
}
+ (S3Region *)usStandard {
return [[[S3Region alloc] initWithBucketNameSuffix:@""
legacyBucketNameSuffix:@""
locationConstraint:nil
endpoint:@"s3.amazonaws.com"
displayName:@"US Standard"
dollarsPerGBMonthStandard:.125
dollarsPerGBMonthRRS:.093] autorelease];
}
+ (S3Region *)usWestNorthernCalifornia {
return [[[S3Region alloc] initWithBucketNameSuffix:@"-us-west-1"
legacyBucketNameSuffix:@"us-west-1"
locationConstraint:@"us-west-1"
endpoint:@"s3-us-west-1.amazonaws.com"
displayName:@"US West (Northern California)"
dollarsPerGBMonthStandard:.140
dollarsPerGBMonthRRS:.103] autorelease];
}
+ (S3Region *)usWestOregon {
return [[[S3Region alloc] initWithBucketNameSuffix:@"-us-west-2"
legacyBucketNameSuffix:@"us-west-2"
locationConstraint:@"us-west-2"
endpoint:@"s3-us-west-2.amazonaws.com"
displayName:@"US West (Oregon)"
dollarsPerGBMonthStandard:.125
dollarsPerGBMonthRRS:.093] autorelease];
}
+ (S3Region *)euIreland {
return [[[S3Region alloc] initWithBucketNameSuffix:@"-eu"
legacyBucketNameSuffix:@"eu"
locationConstraint:@"EU"
endpoint:@"s3-eu-west-1.amazonaws.com"
displayName:@"EU (Ireland)"
dollarsPerGBMonthStandard:.125
dollarsPerGBMonthRRS:.093] autorelease];
}
+ (S3Region *)asiaPacificSingapore {
return [[[S3Region alloc] initWithBucketNameSuffix:@"-ap-southeast-1"
legacyBucketNameSuffix:@"ap-southeast-1"
locationConstraint:@"ap-southeast-1"
endpoint:@"s3-ap-southeast-1.amazonaws.com"
displayName:@"Asia Pacific (Singapore)"
dollarsPerGBMonthStandard:.125
dollarsPerGBMonthRRS:.093] autorelease];
}
+ (S3Region *)asiaPacificTokyo {
return [[[S3Region alloc] initWithBucketNameSuffix:@"-ap-northeast-1"
legacyBucketNameSuffix:@"ap-northeast-1"
locationConstraint:@"ap-northeast-1"
endpoint:@"s3-ap-northeast-1.amazonaws.com"
displayName:@"Asia Pacific (Tokyo)"
dollarsPerGBMonthStandard:.130
dollarsPerGBMonthRRS:.100] autorelease];
}
+ (S3Region *)southAmericaSaoPaulo {
return [[[S3Region alloc] initWithBucketNameSuffix:@"-sa-east-1"
legacyBucketNameSuffix:@"sa-east-1"
locationConstraint:@"sa-east-1"
endpoint:@"s3-sa-east-1.amazonaws.com"
displayName:@"South America (Sao Paulo)"
dollarsPerGBMonthStandard:.170
dollarsPerGBMonthRRS:.127] autorelease];
}
- (void)dealloc {
[bucketNameSuffix release];
[displayName release];
[super dealloc];
}
- (NSString *)bucketNameSuffix {
return bucketNameSuffix;
}
- (NSString *)legacyBucketNameSuffix {
return legacyBucketNameSuffix;
}
- (NSString *)locationConstraint {
return locationConstraint;
}
- (NSString *)endpoint {
return endpoint;
}
- (NSString *)displayName {
return displayName;
}
- (double)dollarsPerGBMonthStandard {
return dollarsPerGBMonthStandard;
}
- (double)dollarsPerGBMonthRRS {
return dollarsPerGBMonthRRS;
}
- (NSString *)bucketNameForAccessKeyID:(NSString *)theAccessKeyID {
return [[theAccessKeyID lowercaseString] stringByAppendingFormat:@"comhaystacksoftwarearq%@", bucketNameSuffix];
}
- (NSString *)description {
return displayName;
}
@end
@implementation S3Region (internal)
- (id)initWithBucketNameSuffix:(NSString *)theBucketNameSuffix
legacyBucketNameSuffix:(NSString *)theLegacyBucketNameSuffix
locationConstraint:(NSString *)theLocationConstraint
endpoint:(NSString *)theEndpoint
displayName:(NSString *)theDisplayName
dollarsPerGBMonthStandard:(double)theDollarsPerGBMonthStandard
dollarsPerGBMonthRRS:(double)theDollarsPerGBMonthRRS {
if (self = [super init]) {
bucketNameSuffix = [theBucketNameSuffix retain];
legacyBucketNameSuffix = [theLegacyBucketNameSuffix retain];
locationConstraint = [theLocationConstraint retain];
endpoint = [theEndpoint retain];
displayName = [theDisplayName retain];
dollarsPerGBMonthStandard = theDollarsPerGBMonthStandard;
dollarsPerGBMonthRRS = theDollarsPerGBMonthRRS;
}
return self;
}
@end

View file

@ -34,26 +34,27 @@
@class S3AuthorizationProvider;
@class ServerBlob;
@class Blob;
@protocol HTTPConnectionDelegate;
@class HTTPTimeoutSetting;
@interface S3Request : NSObject {
NSString *method;
NSString *path;
NSString *s3BucketName;
NSString *queryString;
NSURL *url;
S3AuthorizationProvider *sap;
BOOL withSSL;
BOOL retryOnTransientError;
id urlConnectionDelegate;
HTTPTimeoutSetting *httpTimeoutSetting;
id <HTTPConnectionDelegate> httpConnectionDelegate;
Blob *blob;
NSData *blobData;
uint64_t length;
NSString *virtualHost;
NSString *virtualPath;
NSMutableDictionary *extraHeaders;
unsigned long long bytesUploaded;
}
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry;
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry urlConnectionDelegate:(id)theURLConnectionDelegate;
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry error:(NSError **)error;
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate error:(NSError **)error;
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate httpTimeoutSetting:(HTTPTimeoutSetting *)theTimeoutSetting error:(NSError **)error;
- (void)setBlob:(Blob *)theBlob length:(uint64_t)theLength;
- (void)setHeader:(NSString *)value forKey:(NSString *)key;
- (ServerBlob *)newServerBlob:(NSError **)error;

View file

@ -41,6 +41,9 @@
#import "NSError_extra.h"
#import "S3AuthorizationProvider.h"
#import "NSError_S3.h"
#import "S3Region.h"
#import "HTTPConnectionFactory.h"
#import "HTTPTimeoutSetting.h"
#define INITIAL_RETRY_SLEEP (0.5)
@ -52,35 +55,51 @@
@end
@implementation S3Request
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry {
return [self initWithMethod:theMethod path:thePath queryString:theQueryString authorizationProvider:theSAP useSSL:ssl retryOnTransientError:retry urlConnectionDelegate:nil];
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry error:(NSError **)error {
return [self initWithMethod:theMethod path:thePath queryString:theQueryString authorizationProvider:theSAP useSSL:ssl retryOnTransientError:retry httpConnectionDelegate:nil error:error];
}
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry urlConnectionDelegate:(id)theURLConnectionDelegate {
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate error:(NSError **)error {
HTTPTimeoutSetting *theTimeoutSetting = [[[HTTPTimeoutSetting alloc] init] autorelease];
return [self initWithMethod:theMethod path:thePath queryString:theQueryString authorizationProvider:theSAP useSSL:ssl retryOnTransientError:retry httpConnectionDelegate:theHTTPConnectionDelegate httpTimeoutSetting:theTimeoutSetting error:error];
}
- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry httpConnectionDelegate:(id <HTTPConnectionDelegate>)theHTTPConnectionDelegate httpTimeoutSetting:(HTTPTimeoutSetting *)theTimeoutSetting error:(NSError **)error {
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;
retryOnTransientError = retry;
urlConnectionDelegate = theURLConnectionDelegate; // Don't retain it.
httpTimeoutSetting = [theTimeoutSetting retain];
httpConnectionDelegate = theHTTPConnectionDelegate; // Don't retain it.
extraHeaders = [[NSMutableDictionary alloc] init];
NSString *endpoint = nil;
if ([thePath isEqualToString:@"/"]) {
endpoint = [[S3Region usStandard] endpoint];
} else {
NSRange s3BucketRange = [thePath rangeOfRegex:@"^/([^/]+)" capture:1];
NSAssert(s3BucketRange.location != NSNotFound, @"invalid path -- missing s3 bucket name!");
NSString *s3BucketName = [thePath substringWithRange:s3BucketRange];
endpoint = [[S3Region s3RegionForBucketName:s3BucketName] endpoint];
}
if (theQueryString != nil) {
thePath = [thePath stringByAppendingString:theQueryString];
}
NSString *urlString = [NSString stringWithFormat:@"http%@://%@%@", (ssl ? @"s" : @""), endpoint, thePath];
url = [[NSURL alloc] initWithString:urlString];
if (url == nil) {
SETNSERROR([S3Service errorDomain], -1, @"invalid URL: %@", urlString);
[self release];
return nil;
}
}
return self;
}
- (void)dealloc {
[method release];
[path release];
[queryString release];
[url release];
[httpTimeoutSetting release];
[sap release];
[blob release];
[blobData release];
[virtualHost release];
[virtualPath release];
[extraHeaders release];
[super dealloc];
}
@ -95,30 +114,6 @@
[extraHeaders setObject:value forKey:key];
}
- (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([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"invalid path-style path -- missing s3 bucket name");
return nil;
}
NSRange pathRange = [path rangeOfRegex:pattern capture:2];
if (pathRange.location == NSNotFound) {
SETNSERROR([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"invalid path-style path -- missing path");
return nil;
}
virtualHost = [[[path substringWithRange:s3BucketRange] stringByAppendingString:@".s3.amazonaws.com"] retain];
virtualPath = [[path substringWithRange:pathRange] retain];
}
[blobData release];
blobData = nil;
if (blob != nil) {
@ -131,7 +126,6 @@
NSTimeInterval sleepTime = INITIAL_RETRY_SLEEP;
ServerBlob *sb = nil;
NSError *myError = nil;
BOOL loggedRetry = NO;
for (;;) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
@ -141,18 +135,28 @@
sb = [self newServerBlobOnce:&myError];
if (sb != nil) {
break;
} else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) {
}
if ([myError isSSLError]) {
HSLogError(@"SSL error: %@", myError);
[myError logSSLCerts];
}
if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) {
break;
} else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_TEMPORARY_REDIRECT]) {
NSString *location = [[myError userInfo] objectForKey:@"location"];
HSLogDebug(@"redirecting %@ to %@", url, location);
[url release];
url = [[NSURL alloc] initWithString:location];
if (url == nil) {
HSLogError(@"invalid redirect URL %@", location);
myError = [NSError errorWithDomain:[S3Service errorDomain] code:-1 description:[NSString stringWithFormat:@"invalid redirect URL %@", location]];
break;
}
} else if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR]) {
int httpStatusCode = [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue];
NSString *amazonCode = [[myError userInfo] objectForKey:@"AmazonCode"];
if (httpStatusCode == HTTP_MOVED_TEMPORARILY) {
[virtualHost release];
virtualHost = [[[myError userInfo] objectForKey:@"AmazonEndpoint"] copy];
HSLogInfo(@"S3 redirect to %@", virtualHost);
} else if (retryOnTransientError && [amazonCode isEqualToString:@"RequestTimeout"]) {
if (retryOnTransientError && [amazonCode isEqualToString:@"RequestTimeout"]) {
transientError = YES;
} else if (httpStatusCode == HTTP_INTERNAL_SERVER_ERROR) {
@ -164,27 +168,21 @@
needSleep = YES;
} else {
HSLogError(@"%@ %@ (blob %@): %@", method, virtualPath, blob, myError);
HSLogError(@"%@ %@ (blob %@): %@", method, url, blob, myError);
break;
}
} else if ([myError isConnectionResetError]) {
transientError = YES;
} else if (retryOnTransientError && [myError isTransientError]) {
transientError = YES;
needSleep = YES;
} else {
HSLogError(@"%@ %@ (blob %@): %@", method, virtualPath, blob, myError);
HSLogError(@"%@ %@ (blob %@): %@", method, url, blob, myError);
break;
}
if (transientError) {
if (!loggedRetry) {
HSLogWarn(@"retrying %@ %@ (request body %@): %@", method, virtualPath, blob, myError);
loggedRetry = YES;
} else {
HSLogDebug(@"retrying %@ %@ (request body %@): %@", method, virtualPath, blob, myError);
}
HSLogWarn(@"retrying %@ %@ (request body %@): %@", method, url, blob, myError);
}
if (needSleep) {
[NSThread sleepForTimeInterval:sleepTime];
@ -204,11 +202,7 @@
@implementation S3Request (internal)
- (ServerBlob *)newServerBlobOnce:(NSError **)error {
NSString *urlString = [NSString stringWithFormat:@"http%@://%@%@", (withSSL ? @"s" : @""), virtualHost, [virtualPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
if (queryString) {
urlString = [urlString stringByAppendingString:queryString];
}
id <HTTPConnection> conn = [[[URLConnection alloc] initWithURL:[NSURL URLWithString:urlString] method:method delegate:urlConnectionDelegate] autorelease];
id <HTTPConnection> conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:method httpTimeoutSetting:httpTimeoutSetting httpConnectionDelegate:httpConnectionDelegate] autorelease];
if (conn == nil) {
return nil;
}
@ -226,39 +220,59 @@
for (NSString *headerKey in [extraHeaders allKeys]) {
[conn setRequestHeader:[extraHeaders objectForKey:headerKey] forKey:headerKey];
}
if (![sap setAuthorizationRequestHeaderOnHTTPConnection:conn usingS3BucketName:s3BucketName error:error]) {
if (![sap setAuthorizationRequestHeaderOnHTTPConnection:conn error:error]) {
return nil;
}
bytesUploaded = 0;
HSLogDebug(@"%@ %@", method, url);
BOOL execRet = [conn executeRequestWithBody:blobData error:error];
if (!execRet) {
HSLogDebug(@"executeRequestWithBody failed");
return nil;
}
ServerBlob *ret = nil;
id <InputStream> bodyStream = [conn newResponseBodyStream:error];
if (bodyStream == nil) {
HSLogDebug(@"newResponseBodyStream failed");
return nil;
}
int code = [conn responseCode];
if (code >= 200 && code <= 299) {
ret = [[ServerBlob alloc] initWithInputStream:bodyStream mimeType:[conn responseContentType] downloadName:[conn responseDownloadName]];
[bodyStream release];
return ret;
}
NSData *response = [bodyStream slurp:error];
[bodyStream release];
if (response == nil) {
return nil;
}
int code = [conn responseCode];
if (code >= 200 && code <= 299) {
ret = [[ServerBlob alloc] initWithData:response mimeType:[conn responseContentType] downloadName:[conn responseDownloadName]];
HSLogDebug(@"HTTP %d; returning response length=%d", code, [response length]);
return ret;
}
HSLogTrace(@"http response body: %@", [[[NSString alloc] initWithBytes:[response bytes] length:[response length] encoding:NSUTF8StringEncoding] autorelease]);
if (code == HTTP_NOT_FOUND) {
SETNSERROR([S3Service errorDomain], ERROR_NOT_FOUND, @"%@ not found", path);
SETNSERROR([S3Service errorDomain], ERROR_NOT_FOUND, @"%@ not found", url);
HSLogDebug(@"returning not-found error");
return nil;
}
if (code == HTTP_METHOD_NOT_ALLOWED) {
HSLogError(@"%@ 405 error", url);
SETNSERROR([S3Service errorDomain], ERROR_RRS_NOT_FOUND, @"%@ 405 error", url);
}
if (code == HTTP_MOVED_TEMPORARILY) {
NSString *location = [conn responseHeaderForKey:@"Location"];
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:location forKey:@"location"];
NSError *myError = [NSError errorWithDomain:[S3Service errorDomain] code:ERROR_TEMPORARY_REDIRECT userInfo:userInfo];
if (error != NULL) {
*error = myError;
}
HSLogDebug(@"returning moved-temporarily error");
return nil;
}
NSError *myError = [NSError amazonErrorWithHTTPStatusCode:code responseBody:response];
HSLogDebug(@"%@ %@: %@", method, conn, myError);
HSLogDebug(@"%@ %@ error: %@", method, conn, myError);
if (error != NULL) {
*error = myError;
}

View file

@ -42,31 +42,20 @@
#define S3_RETRY_SLEEP_GROWTH_FACTOR (1.5)
#define S3_MAX_RETRY (5)
enum {
BUCKET_REGION_US_STANDARD = 0,
BUCKET_REGION_US_WEST = 1,
BUCKET_REGION_EU = 2,
BUCKET_REGION_AP_SOUTHEAST_1 = 3,
BUCKET_REGION_AP_NORTHEAST_1 = 4
};
enum {
S3SERVICE_ERROR_UNEXPECTED_RESPONSE = -51001,
S3SERVICE_ERROR_AMAZON_ERROR = -51002,
S3SERVICE_INVALID_PARAMETERS = -51003
};
@interface S3Service : NSObject {
@interface S3Service : NSObject <NSCopying> {
S3AuthorizationProvider *sap;
BOOL useSSL;
BOOL retryOnTransientError;
}
+ (NSString *)errorDomain;
+ (NSString *)displayNameForBucketRegion:(int)region;
+ (NSString *)s3BucketNameForAccessKeyID:(NSString *)theAccessKeyId region:(int)s3BucketRegion;
+ (NSArray *)s3BucketNamesForAccessKeyID:(NSString *)theAccessKeyId;
+ (int)s3BucketRegionForS3BucketName:(NSString *)s3BucketName;
- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)useSSL retryOnTransientError:(BOOL)retry;
- (S3Owner *)s3Owner:(NSError **)error;
- (NSArray *)s3BucketNames:(NSError **)error;
- (BOOL)s3BucketExists:(NSString *)s3BucketName;
@ -82,4 +71,6 @@ enum {
- (BOOL)aclXMLData:(NSData **)aclXMLData atPath:(NSString *)path error:(NSError **)error;
- (BOOL)acl:(int *)acl atPath:(NSString *)path error:(NSError **)error;
- (S3AuthorizationProvider *)s3AuthorizationProvider;
@end

View file

@ -35,6 +35,7 @@
#import "Blob.h"
#import "RegexKitLite.h"
#import "NSError_S3.h"
#import "S3Owner.h"
#import "S3Lister.h"
#import "S3AuthorizationProvider.h"
#import "S3Service.h"
@ -65,58 +66,7 @@
+ (NSString *)errorDomain {
return @"S3ServiceErrorDomain";
}
+ (NSString *)displayNameForBucketRegion:(int)region {
switch (region) {
case BUCKET_REGION_US_STANDARD:
return @"US Standard";
case BUCKET_REGION_US_WEST:
return @"US-West (Northern California)";
case BUCKET_REGION_AP_SOUTHEAST_1:
return @"Asia Pacific (Singapore)";
case BUCKET_REGION_AP_NORTHEAST_1:
return @"Asia Pacific (Japan)";
case BUCKET_REGION_EU:
return @"EU (Ireland)";
}
NSAssert(NO, @"invalid S3 bucket region");
return nil;
}
+ (NSString *)s3BucketNameForAccessKeyID:(NSString *)theAccessKeyID region:(int)s3BucketRegion {
NSString *regionSuffix = @"";
if (s3BucketRegion == BUCKET_REGION_US_WEST) {
regionSuffix = @".us-west-1";
} else if (s3BucketRegion == BUCKET_REGION_AP_SOUTHEAST_1) {
regionSuffix = @".ap-southeast-1";
} else if (s3BucketRegion == BUCKET_REGION_AP_NORTHEAST_1) {
regionSuffix = @".ap-northeast-1";
} else if (s3BucketRegion == BUCKET_REGION_AP_NORTHEAST_1) {
regionSuffix = @".ap-northeast-1";
} else if (s3BucketRegion == BUCKET_REGION_EU) {
regionSuffix = @".eu";
}
return [NSString stringWithFormat:@"%@.com.haystacksoftware.arq%@", [theAccessKeyID lowercaseString], regionSuffix];
}
+ (int)s3BucketRegionForS3BucketName:(NSString *)s3BucketName {
if ([s3BucketName hasSuffix:@"com.haystacksoftware.arq.eu"]) {
return BUCKET_REGION_EU;
} else if ([s3BucketName hasSuffix:@"com.haystacksoftware.arq.ap-southeast-1"]) {
return BUCKET_REGION_AP_SOUTHEAST_1;
} else if ([s3BucketName hasSuffix:@"com.haystacksoftware.arq.ap-northeast-1"]) {
return BUCKET_REGION_AP_NORTHEAST_1;
} else if ([s3BucketName hasSuffix:@"com.haystacksoftware.arq.us-west-1"]) {
return BUCKET_REGION_US_WEST;
}
return BUCKET_REGION_US_STANDARD;
}
+ (NSArray *)s3BucketNamesForAccessKeyID:(NSString *)theAccessKeyId {
return [NSArray arrayWithObjects:
[S3Service s3BucketNameForAccessKeyID:theAccessKeyId region:BUCKET_REGION_US_STANDARD],
[S3Service s3BucketNameForAccessKeyID:theAccessKeyId region:BUCKET_REGION_US_WEST],
[S3Service s3BucketNameForAccessKeyID:theAccessKeyId region:BUCKET_REGION_EU],
[S3Service s3BucketNameForAccessKeyID:theAccessKeyId region:BUCKET_REGION_AP_SOUTHEAST_1],
[S3Service s3BucketNameForAccessKeyID:theAccessKeyId region:BUCKET_REGION_AP_NORTHEAST_1],
nil];
}
- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)isUseSSL retryOnTransientError:(BOOL)retry {
if (self = [super init]) {
sap = [theSAP retain];
@ -129,6 +79,36 @@
[sap release];
[super dealloc];
}
- (S3Owner *)s3Owner:(NSError **)error {
if (error) {
*error = 0;
}
NSXMLDocument *doc = [self listBuckets:error];
if (!doc) {
return nil;
}
NSXMLElement *rootElem = [doc rootElement];
NSArray *idNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Owner/ID" error:error];
if (!idNodes) {
return nil;
}
if ([idNodes count] == 0) {
HSLogError(@"ListAllMyBucketsResult/Owner/ID node not found");
return nil;
}
NSXMLNode *ownerIDNode = [idNodes objectAtIndex:0];
NSArray *displayNameNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Owner/DisplayName" error:error];
if (!displayNameNodes) {
return nil;
}
if ([displayNameNodes count] == 0) {
HSLogError(@"ListAllMyBucketsResult/Owner/DisplayName not found");
return nil;
}
NSXMLNode *displayNameNode = [displayNameNodes objectAtIndex:0];
HSLogDebug(@"s3 owner ID: %@", [displayNameNode stringValue]);
return [[[S3Owner alloc] initWithDisplayName:[displayNameNode stringValue] idString:[ownerIDNode stringValue]] autorelease];
}
- (NSArray *)s3BucketNames:(NSError **)error {
NSXMLDocument *doc = [self listBuckets:error];
if (!doc) {
@ -192,7 +172,10 @@
}
- (BOOL)containsBlob:(BOOL *)contains atPath:(NSString *)path dataSize:(unsigned long long *)dataSize error:(NSError **)error {
BOOL ret = YES;
S3Request *s3r = [[S3Request alloc] initWithMethod:@"HEAD" path:path queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError];
S3Request *s3r = [[S3Request alloc] initWithMethod:@"HEAD" path:path queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error];
if (s3r == nil) {
return NO;
}
NSError *myError = nil;
ServerBlob *sb = [s3r newServerBlob:&myError];
if (sb != nil) {
@ -201,6 +184,9 @@
} else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) {
*contains = NO;
HSLogDebug(@"S3 path %@ does NOT exist", path);
} else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_RRS_NOT_FOUND]) {
*contains = NO;
HSLogDebug(@"S3 path %@ returns 405 error", path);
} else {
*contains = NO;
ret = NO;
@ -222,7 +208,10 @@
}
- (ServerBlob *)newServerBlobAtPath:(NSString *)path error:(NSError **)error {
HSLogDebug(@"getting %@", path);
S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:path queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError];
S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:path queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error];
if (s3r == nil) {
return nil;
}
ServerBlob *sb = [s3r newServerBlob:error];
[s3r release];
return sb;
@ -230,7 +219,10 @@
- (BOOL)aclXMLData:(NSData **)aclXMLData atPath:(NSString *)path error:(NSError **)error {
*aclXMLData = nil;
HSLogDebug(@"getting %@", path);
S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:path queryString:@"?acl" authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError];
S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:path queryString:@"?acl" authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error];
if (s3r == nil) {
return NO;
}
ServerBlob *sb = [s3r newServerBlob:error];
[s3r release];
if (sb == nil) {
@ -260,11 +252,24 @@
}
return ret;
}
- (S3AuthorizationProvider *)s3AuthorizationProvider {
return sap;
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
return [[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError];
}
@end
@implementation S3Service (internal)
- (NSXMLDocument *)listBuckets:(NSError **)error {
S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:@"/" queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError];
S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:@"/" queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error];
if (s3r == nil) {
return nil;
}
ServerBlob *sb = [s3r newServerBlob:error];
[s3r release];
if (sb == nil) {
@ -275,17 +280,26 @@
if (data == nil) {
return nil;
}
return [[[NSXMLDocument alloc] initWithData:data options:0 error:error] autorelease];
NSError *myError = nil;
NSXMLDocument *ret = [[[NSXMLDocument alloc] initWithData:data options:0 error:&myError] autorelease];
if (ret == nil) {
HSLogDebug(@"error parsing List Buckets result XML %@", [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease]);
SETNSERROR([S3Service errorDomain], [myError code], @"error parsing S3 List Buckets result XML: %@", [myError description]);
}
return ret;
}
- (BOOL)internalACL:(int *)acl atPath:(NSString *)path error:(NSError **)error {
NSData *aclData;
if (![self aclXMLData:&aclData atPath:path error:error]) {
return NO;
}
NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:aclData options:0 error:error] autorelease];
NSError *myError = nil;
NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:aclData options:0 error:&myError] autorelease];
if (!xmlDoc) {
SETNSERROR([S3Service errorDomain], [myError code], @"error parsing S3 Get ACL response: %@", myError);
return NO;
}
HSLogTrace(@"ACL XML: %@", xmlDoc);
NSArray *grants = [xmlDoc nodesForXPath:@"AccessControlPolicy/AccessControlList/Grant" error:error];
if (!grants) {
return NO;

40
shared/DNS_SDErrors.h Normal file
View file

@ -0,0 +1,40 @@
/*
Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Cocoa/Cocoa.h>
@interface DNS_SDErrors : NSObject {
}
+ (NSString *)descriptionForDNS_SDError:(int)code;
@end

101
shared/DNS_SDErrors.m Normal file
View file

@ -0,0 +1,101 @@
/*
Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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

View file

@ -43,4 +43,8 @@
#define ERROR_ACCESS_DENIED (-12)
#define ERROR_MISSING_REQUIRED_OBJECT (-13)
#define ERROR_PAUSE_REQUESTED (-14)
#define ERROR_HELPER_CRASHED (-15)
#define ERROR_HELPER_CRASHED (-15)
#define ERROR_TEMPORARY_REDIRECT (-16)
#define ERROR_RRS_NOT_FOUND (-17)
#define ERROR_INVALID_FILE (-18)
#define ERROR_DELAYS_IN_S3_EVENTUAL_CONSISTENCY (-19)

View file

@ -36,5 +36,8 @@
+ (NSError *)errorWithDomain:(NSString *)domain code:(NSInteger)code description:(NSString *)theDescription;
- (id)initWithDomain:(NSString *)domain code:(NSInteger)code description:(NSString *)theDescription;
- (BOOL)isErrorWithDomain:(NSString *)theDomain code:(int)theCode;
- (BOOL)isConnectionResetError;
- (BOOL)isTransientError;
- (BOOL)isSSLError;
- (void)logSSLCerts;
@end

View file

@ -30,26 +30,55 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Security/Security.h>
#import <Security/SecCertificate.h>
#import "NSError_extra.h"
#import "NSErrorCodes.h"
@implementation NSError (extra)
+ (NSError *)errorWithDomain:(NSString *)domain code:(NSInteger)code description:(NSString *)theDescription {
if (theDescription == nil) {
theDescription = @"(missing description)";
}
return [NSError errorWithDomain:domain code:code userInfo:[NSDictionary dictionaryWithObject:theDescription forKey:NSLocalizedDescriptionKey]];
}
- (id)initWithDomain:(NSString *)domain code:(NSInteger)code description:(NSString *)theDescription {
if (theDescription == nil) {
theDescription = @"(missing description)";
}
return [self initWithDomain:domain code:code userInfo:[NSDictionary dictionaryWithObject:theDescription forKey:NSLocalizedDescriptionKey]];
}
- (BOOL)isErrorWithDomain:(NSString *)theDomain code:(int)theCode {
return [self code] == theCode && [[self domain] isEqualToString:theDomain];
}
- (BOOL)isConnectionResetError {
if ([[self domain] isEqualToString:@"UnixErrorDomain"] || [[self domain] isEqualToString:@"NSPOSIXErrorDomain"]) {
if ([self code] == ENETRESET
|| [self code] == ECONNRESET) {
return YES;
}
}
return NO;
}
- (BOOL)isTransientError {
if ([[self domain] isEqualToString:@"UnixErrorDomain"] && [self code] == ETIMEDOUT) {
return YES;
if ([[self domain] isEqualToString:@"UnixErrorDomain"] || [[self domain] isEqualToString:@"NSPOSIXErrorDomain"]) {
if ([self code] == ENETDOWN
|| [self code] == EADDRNOTAVAIL
|| [self code] == ENETUNREACH
|| [self code] == ENETRESET
|| [self code] == ECONNABORTED
|| [self code] == ECONNRESET
|| [self code] == EISCONN
|| [self code] == ENOTCONN
|| [self code] == ETIMEDOUT
|| [self code] == EHOSTUNREACH
|| [self code] == EHOSTDOWN
|| [self code] == EPIPE) {
return YES;
}
}
if ([[self domain] isEqualToString:@"NSPOSIXErrorDomain"] && [self code] == ETIMEDOUT) {
return YES;
}
if ([[self domain] isEqualToString:@"NSPOSIXErrorDomain"] && [self code] == ENOTCONN) {
if ([[self domain] isEqualToString:(NSString *)kCFErrorDomainCFNetwork]) {
return YES;
}
if ([[self domain] isEqualToString:NSURLErrorDomain]) {
@ -63,7 +92,55 @@
return YES;
}
}
if ([[self domain] isEqualToString:@"NSOSStatusErrorDomain"] && [self code] <= errSSLProtocol && [self code] >= errSSLLast) {
return YES;
}
if ([self isSSLError]) {
return YES;
}
if ([self code] == ERROR_TIMEOUT) {
return YES;
}
HSLogDebug(@"%@ not a transient error", self);
return NO;
}
- (BOOL)isSSLError {
return [[self domain] isEqualToString:NSURLErrorDomain]
&& [self code] <= NSURLErrorSecureConnectionFailed
&& [self code] >= NSURLErrorClientCertificateRejected;
}
- (void)logSSLCerts {
NSArray *certs = [[self userInfo] objectForKey:@"NSErrorPeerCertificateChainKey"];
for (NSUInteger index = 0; index < [certs count]; index++) {
SecCertificateRef secCert = (SecCertificateRef)[certs objectAtIndex:index];
CFStringRef commonName = NULL;
if (SecCertificateCopyCommonName(secCert, &commonName) == 0 && commonName != NULL) {
HSLog(@"SSL cert common name: %@", (NSString *)commonName);
CFRelease(commonName);
} else {
HSLog(@"error getting SSL cert's common name");
}
SecKeyRef key = NULL;
if (SecCertificateCopyPublicKey(secCert, &key) == 0 && key != NULL) {
CSSM_CSP_HANDLE cspHandle;
if (SecKeyGetCSPHandle(key, &cspHandle) == 0) {
HSLog(@"SSL cert CSP handle: %d", (long)cspHandle);
} else {
HSLog(@"error getting SSL cert's key's CSP handle");
}
const CSSM_KEY *cssmKey;
if (SecKeyGetCSSMKey(key, &cssmKey) == 0) {
NSData *keyHeaderData = [NSData dataWithBytes:(const unsigned char *)&(cssmKey->KeyHeader) length:sizeof(CSSM_KEYHEADER)];
HSLog(@"SSL cert CSSM key header: %@", keyHeaderData);
NSData *keyData = [NSData dataWithBytes:(const unsigned char *)cssmKey->KeyData.Data length:cssmKey->KeyData.Length];
HSLog(@"SSL cert CSSM key data: %@", keyData);
} else {
HSLog(@"error getting SSL cert's key's CSM key");
}
CFRelease(key);
} else {
HSLog(@"error getting SSL cert's public key");
}
}
}
@end

40
shared/Sysctl.h Normal file
View file

@ -0,0 +1,40 @@
/*
Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Cocoa/Cocoa.h>
@interface Sysctl : NSObject {
}
+ (BOOL)networkBytesIn:(unsigned long long *)bytesIn bytesOut:(unsigned long long *)bytsOut error:(NSError **)error;
@end

78
shared/Sysctl.m Normal file
View file

@ -0,0 +1,78 @@
/*
Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of PhotoMinds LLC or Haystack Software, nor the names of
their contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <sys/sysctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/route.h>
#import "Sysctl.h"
#import "SetNSError.h"
@implementation Sysctl
+ (BOOL)networkBytesIn:(unsigned long long *)bytesIn bytesOut:(unsigned long long *)bytesOut error:(NSError **)error {
int mib[] = {
CTL_NET,
PF_ROUTE,
0,
0,
NET_RT_IFLIST2,
0
};
size_t len;
if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
int errnum = errno;
SETNSERROR(@"UnixErrorDomain", errnum, @"sysctl: %s", strerror(errnum));
return NO;
}
char *buf = (char *)malloc(len);
if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
int errnum = errno;
SETNSERROR(@"UnixErrorDomain", errnum, @"sysctl: %s", strerror(errnum));
return NO;
}
char *lim = buf + len;
char *next = NULL;
*bytesIn = 0;
*bytesOut = 0;
for (next = buf; next < lim; ) {
struct if_msghdr *ifm = (struct if_msghdr *)next;
next += ifm->ifm_msglen;
if (ifm->ifm_type == RTM_IFINFO2) {
struct if_msghdr2 *if2m = (struct if_msghdr2 *)ifm;
*bytesIn += if2m->ifm_data.ifi_ibytes;
*bytesOut += if2m->ifm_data.ifi_obytes;
}
}
free(buf);
return YES;
}
@end