mirror of
https://github.com/samsonjs/arq_restore.git
synced 2026-03-25 09:25:53 +00:00
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:
parent
0009121dcc
commit
40ef06c9d2
35 changed files with 2436 additions and 173 deletions
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
83
http/CFHTTPConnection.h
Normal 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
539
http/CFHTTPConnection.m
Normal 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
28
http/CFHTTPInputStream.h
Normal 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
135
http/CFHTTPInputStream.m
Normal 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
41
http/CFNetwork.h
Normal 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
130
http/CFNetwork.m
Normal 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
|
||||
19
http/HTTPConnectionDelegate.h
Normal file
19
http/HTTPConnectionDelegate.h
Normal 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
|
||||
27
http/HTTPConnectionFactory.h
Normal file
27
http/HTTPConnectionFactory.h
Normal 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
|
||||
163
http/HTTPConnectionFactory.m
Normal file
163
http/HTTPConnectionFactory.m
Normal 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
16
http/HTTPTimeoutSetting.h
Normal 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
35
http/HTTPTimeoutSetting.m
Normal 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
48
io/NetMonitor.h
Normal 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
132
io/NetMonitor.m
Normal 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
20
s3/RemoteS3Signer.h
Normal 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
75
s3/RemoteS3Signer.m
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
43
s3/S3Owner.h
Normal 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
50
s3/S3Owner.m
Normal 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
39
s3/S3Region.h
Normal 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
162
s3/S3Region.m
Normal 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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
162
s3/S3Request.m
162
s3/S3Request.m
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
130
s3/S3Service.m
130
s3/S3Service.m
|
|
@ -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
40
shared/DNS_SDErrors.h
Normal 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
101
shared/DNS_SDErrors.m
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
40
shared/Sysctl.h
Normal 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
78
shared/Sysctl.m
Normal 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
|
||||
Loading…
Reference in a new issue