fixed bug that form data request can't add custom Content-Type to header #356

Open
wants to merge 4 commits into from
View
4 Classes/ASIDataCompressor.m
@@ -161,7 +161,7 @@ + (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinati
readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
// Make sure nothing went wrong
- if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
+ if ([inputStream streamStatus] == NSStreamStatusError) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
}
@@ -187,7 +187,7 @@ + (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinati
[outputStream write:(const uint8_t *)[outputData bytes] maxLength:[outputData length]];
// Make sure nothing went wrong
- if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
+ if ([inputStream streamStatus] == NSStreamStatusError) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
}
View
4 Classes/ASIDataDecompressor.m
@@ -158,7 +158,7 @@ + (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destina
readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
// Make sure nothing went wrong
- if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
+ if ([inputStream streamStatus] == NSStreamStatusError) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
}
@@ -184,7 +184,7 @@ + (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destina
[outputStream write:(Bytef*)[outputData bytes] maxLength:[outputData length]];
// Make sure nothing went wrong
- if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
+ if ([inputStream streamStatus] == NSStreamStatusError) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
}
View
25 Classes/ASIFormDataRequest.m
@@ -213,6 +213,19 @@ - (void)buildPostBody
#endif
}
+- (BOOL)isHeaderContainsContentType
+{
+ BOOL headerContainsContentType = NO;
+ for (id key in self.requestHeaders.allKeys)
+ {
+ if ([key isKindOfClass:[NSString class]] && [key isEqualToString:@"Content-Type"])
+ {
+ headerContainsContentType = YES;
+ break;
+ }
+ }
+ return headerContainsContentType;
+}
- (void)buildMultipartFormDataPostBody
{
@@ -228,8 +241,11 @@ - (void)buildMultipartFormDataPostBody
CFRelease(uuid);
NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString];
- [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]];
-
+ if (![self isHeaderContainsContentType])
+ {
+ [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]];
+ }
+
[self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]];
// Adds post data
@@ -288,7 +304,10 @@ - (void)buildURLEncodedPostBody
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
- [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]];
+ if (![self isHeaderContainsContentType])
+ {
+ [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]];
+ }
NSUInteger i=0;
View
35 Classes/ASIHTTPRequest.m
@@ -1173,8 +1173,10 @@ - (void)startRequest
// Are we gzipping the request body?
if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
+// [self setPostBodyReadStream:[NSInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath]]];
} else {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
+// [self setPostBodyReadStream:[NSInputStream inputStreamWithFileAtPath:[self postBodyFilePath]]];
}
[self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
} else {
@@ -1183,8 +1185,10 @@ - (void)startRequest
if ([self postBody] && [[self postBody] length] > 0) {
if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
+// [self setPostBodyReadStream:[NSInputStream inputStreamWithData:[self compressedPostBody]]];
} else if ([self postBody]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
+// [self setPostBodyReadStream:[NSInputStream inputStreamWithData:[self postBody]]];
}
[self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
@@ -1212,17 +1216,30 @@ - (void)startRequest
// see: http://iphonedevelopment.blogspot.com/2010/05/nsstream-tcp-and-ssl.html
NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
- [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
- [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
- [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
- kCFNull,kCFStreamSSLPeerName,
- nil];
+ [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
+ [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
+ [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
+ kCFNull,kCFStreamSSLPeerName,
+ @"kCFStreamSocketSecurityLevelTLSv1_0SSLv3", kCFStreamSSLLevel,
+ nil];
- CFReadStreamSetProperty((CFReadStreamRef)[self readStream],
- kCFStreamPropertySSLSettings,
+ CFReadStreamSetProperty((CFReadStreamRef)[self readStream],
+ kCFStreamPropertySSLSettings,
(CFTypeRef)sslProperties);
[sslProperties release];
- }
+ } else {
+ NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO], kCFStreamSSLAllowsExpiredCertificates,
+ [NSNumber numberWithBool:NO], kCFStreamSSLAllowsAnyRoot,
+ [NSNumber numberWithBool:YES], kCFStreamSSLValidatesCertificateChain,
+ @"kCFStreamSocketSecurityLevelTLSv1_0SSLv3", kCFStreamSSLLevel,
+ nil];
+
+ CFReadStreamSetProperty((CFReadStreamRef)[self readStream],
+ kCFStreamPropertySSLSettings,
+ (CFTypeRef)sslProperties);
+ [sslProperties release];
+ }
// Tell CFNetwork to use a client certificate
if (clientCertificateIdentity) {
@@ -4866,7 +4883,7 @@ + (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterva
// RFC 2612 says max-age must override any Expires header
if (maxAge) {
- return [[NSDate date] addTimeInterval:maxAge];
+ return [[NSDate date] dateByAddingTimeInterval:maxAge];
} else {
NSString *expires = [responseHeaders objectForKey:@"Expires"];
if (expires) {
View
8 Classes/ASIInputStream.h
@@ -14,13 +14,11 @@
// Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead.
// It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading
-@interface ASIInputStream : NSObject {
- NSInputStream *stream;
- ASIHTTPRequest *request;
-}
+@interface ASIInputStream : NSObject<NSStreamDelegate>
+
+ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request;
+ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request;
+- (id)initWithInputStream:(NSInputStream *)stream;
-@property (retain, nonatomic) NSInputStream *stream;
@property (assign, nonatomic) ASIHTTPRequest *request;
@end
View
218 Classes/ASIInputStream.m
@@ -8,11 +8,21 @@
#import "ASIInputStream.h"
#import "ASIHTTPRequest.h"
+#import <objc/runtime.h>
// Used to ensure only one request can read data at once
static NSLock *readLock = nil;
@implementation ASIInputStream
+{
+ NSInputStream *stream;
+ id<NSStreamDelegate> delegate;
+
+ CFReadStreamClientCallBack copiedCallback;
+ CFStreamClientContext copiedContext;
+ CFOptionFlags requestedEvents;
+ ASIHTTPRequest *request;
+}
+ (void)initialize
{
@@ -23,53 +33,42 @@ + (void)initialize
+ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)theRequest
{
- ASIInputStream *theStream = [[[self alloc] init] autorelease];
+ ASIInputStream *theStream = [[[ASIInputStream alloc] initWithInputStream:[NSInputStream inputStreamWithFileAtPath:path]] autorelease];
[theStream setRequest:theRequest];
- [theStream setStream:[NSInputStream inputStreamWithFileAtPath:path]];
return theStream;
}
+ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)theRequest
{
- ASIInputStream *theStream = [[[self alloc] init] autorelease];
+ ASIInputStream *theStream = [[[ASIInputStream alloc] initWithInputStream:[NSInputStream inputStreamWithData:data]] autorelease];
[theStream setRequest:theRequest];
- [theStream setStream:[NSInputStream inputStreamWithData:data]];
return theStream;
}
-- (void)dealloc
+#pragma mark - Object lifecycle
+
+- (id)initWithInputStream:(NSInputStream *)aStream
{
- [stream release];
- [super dealloc];
+ self = [super init];
+ if (self) {
+ // Initialization code here.
+ stream = [aStream retain];
+ [stream setDelegate:self];
+
+ [self setDelegate:self];
+ }
+
+ return self;
}
-// Called when CFNetwork wants to read more of our request body
-// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read
-- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
+- (void)dealloc
{
- [readLock lock];
- unsigned long toRead = len;
- if ([ASIHTTPRequest isBandwidthThrottled]) {
- toRead = [ASIHTTPRequest maxUploadReadLength];
- if (toRead > len) {
- toRead = len;
- } else if (toRead == 0) {
- toRead = 1;
- }
- [request performThrottling];
- }
- [readLock unlock];
- NSInteger rv = [stream read:buffer maxLength:toRead];
- if (rv > 0)
- [ASIHTTPRequest incrementBandwidthUsedInLastSecond:rv];
- return rv;
+ [stream release];
+ [super dealloc];
}
-/*
- * Implement NSInputStream mandatory methods to make sure they are implemented
- * (necessary for MacRuby for example) and avoid the overhead of method
- * forwarding for these common methods.
- */
+#pragma mark - NSStream subclass methods
+
- (void)open
{
[stream open];
@@ -80,14 +79,19 @@ - (void)close
[stream close];
}
-- (id)delegate
+- (id <NSStreamDelegate> )delegate
{
- return [stream delegate];
+ return delegate;
}
-- (void)setDelegate:(id)delegate
+- (void)setDelegate:(id<NSStreamDelegate>)aDelegate
{
- [stream setDelegate:delegate];
+ if (aDelegate == nil) {
+ delegate = self;
+ }
+ else {
+ delegate = aDelegate;
+ }
}
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
@@ -120,6 +124,149 @@ - (NSError *)streamError
return [stream streamError];
}
+#pragma mark - NSInputStream subclass methods
+
+// Called when CFNetwork wants to read more of our request body
+// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read
+- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
+{
+ [readLock lock];
+ unsigned long toRead = len;
+ if ([ASIHTTPRequest isBandwidthThrottled]) {
+ toRead = [ASIHTTPRequest maxUploadReadLength];
+ if (toRead > len) {
+ toRead = len;
+ } else if (toRead == 0) {
+ toRead = 1;
+ }
+ [request performThrottling];
+ }
+ [readLock unlock];
+ NSInteger rv = [stream read:buffer maxLength:toRead];
+ if (rv > 0)
+ [ASIHTTPRequest incrementBandwidthUsedInLastSecond:rv];
+ return rv;
+}
+
+
+- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len
+{
+ // We cannot implement our character-counting in O(1) time,
+ // so we return NO as indicated in the NSInputStream
+ // documentation.
+ return NO;
+}
+
+- (BOOL)hasBytesAvailable
+{
+ return [stream hasBytesAvailable];
+}
+
+#pragma mark - Undocumented CFReadStream bridged methods
+
++ (BOOL)resolveInstanceMethod:(SEL) selector
+{
+ NSString *name = NSStringFromSelector(selector);
+
+ if ([name hasPrefix:@"_"]){
+ name = [name substringFromIndex:1];
+ SEL aSelector = NSSelectorFromString(name);
+ Method method = class_getInstanceMethod(self, aSelector);
+
+ if (method)
+ {
+ class_addMethod(self,
+ selector,
+ method_getImplementation(method),
+ method_getTypeEncoding(method));
+ return YES;
+ }
+ }
+ return [super resolveInstanceMethod:selector];
+}
+
+- (void)scheduleInCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode
+{
+ CFReadStreamScheduleWithRunLoop((CFReadStreamRef)stream, aRunLoop, aMode);
+}
+
+- (BOOL)setCFClientFlags:(CFOptionFlags)inFlags callback:(CFReadStreamClientCallBack)inCallback context:(CFStreamClientContext *)inContext
+{
+ if (inCallback != NULL) {
+ requestedEvents = inFlags;
+ copiedCallback = inCallback;
+ memcpy(&copiedContext, inContext, sizeof(CFStreamClientContext));
+
+ if (copiedContext.info && copiedContext.retain) {
+ copiedContext.retain(copiedContext.info);
+ }
+ }
+ else {
+ requestedEvents = kCFStreamEventNone;
+ copiedCallback = NULL;
+ if (copiedContext.info && copiedContext.release) {
+ copiedContext.release(copiedContext.info);
+ }
+
+ memset(&copiedContext, 0, sizeof(CFStreamClientContext));
+ }
+
+ return YES;
+}
+
+- (void)unscheduleFromCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode
+{
+ CFReadStreamUnscheduleFromRunLoop((CFReadStreamRef)stream, aRunLoop, aMode);
+}
+
+#pragma mark - NSStreamDelegate methods
+
+- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
+{
+ assert(aStream == stream);
+
+ switch (eventCode) {
+ case NSStreamEventOpenCompleted:
+ if (requestedEvents & kCFStreamEventOpenCompleted) {
+ copiedCallback((CFReadStreamRef)self,
+ kCFStreamEventOpenCompleted,
+ copiedContext.info);
+ }
+ break;
+
+ case NSStreamEventHasBytesAvailable:
+ if (requestedEvents & kCFStreamEventHasBytesAvailable) {
+ copiedCallback((CFReadStreamRef)self,
+ kCFStreamEventHasBytesAvailable,
+ copiedContext.info);
+ }
+ break;
+
+ case NSStreamEventErrorOccurred:
+ if (requestedEvents & kCFStreamEventErrorOccurred) {
+ copiedCallback((CFReadStreamRef)self,
+ kCFStreamEventErrorOccurred,
+ copiedContext.info);
+ }
+ break;
+
+ case NSStreamEventEndEncountered:
+ if (requestedEvents & kCFStreamEventEndEncountered) {
+ copiedCallback((CFReadStreamRef)self,
+ kCFStreamEventEndEncountered,
+ copiedContext.info);
+ }
+ break;
+
+ case NSStreamEventHasSpaceAvailable:
+ // This doesn't make sense for a read stream
+ break;
+
+ default:
+ break;
+ }
+}
+
// If we get asked to perform a method we don't have (probably internal ones),
// we'll just forward the message to our stream
@@ -133,6 +280,5 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation
[anInvocation invokeWithTarget:stream];
}
-@synthesize stream;
@synthesize request;
@end