reimplemented multipart form data to use a body streaming object that aggregates file streams and in-memory data objects to compose the request body just in time. also trimmed the multipart form body public API and added CoreMobileServices framework to enable lookup of MIME type by file extension.

This commit is contained in:
Max Lansing 2012-06-30 16:32:11 -07:00
parent 5e90a17c76
commit ced167f867
3 changed files with 811 additions and 571 deletions

View file

@ -24,7 +24,7 @@
@class AFHTTPRequestOperation;
@protocol AFHTTPClientOperation;
@protocol AFMultipartFormData;
@protocol AFStreamingMultipartFormData;
/**
Posted when network reachability changes.
@ -304,7 +304,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
path:(NSString *)path
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block;
constructingBodyWithBlock:(void (^)(id <AFStreamingMultipartFormData> formData))block;
///-------------------------------
/// @name Creating HTTP Operations
@ -457,15 +457,10 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
@see `AFHTTPClient -multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`
*/
@protocol AFMultipartFormData
/**
Appends HTTP headers, followed by the encoded data and the multipart form boundary.
@param headers The HTTP headers to be appended to the form data.
@param body The data to be encoded and appended to the form data.
*/
- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body;
@protocol AFStreamingMultipartFormData
/**
Appends the HTTP headers `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary.
@ -473,18 +468,9 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
@param data The data to be encoded and appended to the form data.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
*/
- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name;
/**
Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary.
@param data The data to be encoded and appended to the form data.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
@param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`.
@param filename The filename to be associated with the specified data. This parameter must not be `nil`.
*/
- (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType;
/**
Appends the HTTP header `Content-Disposition: file; filename=#{generated filename}; name=#{name}"` and `Content-Type: #{generated mimeType}`, followed by the encoded file data and the multipart form boundary.
@ -496,34 +482,8 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
@discussion The filename and MIME type for this data in the form will be automatically generated, using `NSURLResponse` `-suggestedFilename` and `-MIMEType`, respectively.
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error;
/**
Appends the HTTP header `Content-Disposition: file; filename=#{generated filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary, using NSInputStream to read input.
@param fileURL The URL corresponding to the file whose content will be appended to the form.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
@param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`.
@discussion The filename is generated from the last component of the streamingURL parameter. The size of the buffer used to copy from streamingURL to the output stream is defined by the constant kAFStreamToStreamBufferSize.
*/
- (void)appendPartWithStreamingURL:(NSURL *)streamingURL
name:(NSString *)name
mimeType:(NSString *)mimeType;
/**
Appends encoded data to the form data.
@param data The data to be encoded and appended to the form data.
*/
- (void)appendData:(NSData *)data;
/**
Appends a string to the form data.
@param string The string to be encoded and appended to the form data.
*/
- (void)appendString:(NSString *)string;
@end

View file

@ -30,6 +30,9 @@
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <UIKit/UIKit.h>
#import <MobileCoreServices/MobileCoreServices.h>
#else
#import <CoreServices/CoreServices.h>
#endif
#ifdef _SYSTEMCONFIGURATION_H
@ -41,10 +44,10 @@
#import <netdb.h>
#endif
NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
@interface AFMultipartFormData : NSObject <AFMultipartFormData>
@interface AFStreamingMultipartFormData : NSObject <AFStreamingMultipartFormData>
- (id)initWithURLRequest:(NSMutableURLRequest *)request
stringEncoding:(NSStringEncoding)encoding;
@ -52,6 +55,17 @@ NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire
@end
@interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate>
-(id)initWithStringEncoding:(NSStringEncoding)encoding;
-(BOOL)addFileFromURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error;
-(void)addFormData:(NSData *)data name:(NSString *)name;
-(BOOL)empty;
-(NSUInteger)contentLength;
@end
#pragma mark -
#ifdef _SYSTEMCONFIGURATION_H
@ -454,10 +468,6 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
[request setHTTPMethod:method];
[request setAllHTTPHeaderFields:self.defaultHeaders];
if ([method isEqualToString:@"GET"] || [method isEqualToString:@"HEAD"]) {
[request setHTTPShouldUsePipelining:YES];
}
if (parameters) {
if ([method isEqualToString:@"GET"] || [method isEqualToString:@"HEAD"] || [method isEqualToString:@"DELETE"]) {
url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding)]];
@ -487,12 +497,14 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
path:(NSString *)path
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData>formData))block
constructingBodyWithBlock:(void (^)(id <AFStreamingMultipartFormData>formData))block
{
NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:nil];
__block AFMultipartFormData *formData = [[[AFMultipartFormData alloc] initWithURLRequest:request stringEncoding:self.stringEncoding] autorelease];
if (parameters) {
__block AFStreamingMultipartFormData * formData = [[[AFStreamingMultipartFormData alloc] initWithURLRequest:request stringEncoding:self.stringEncoding] autorelease];
// __block AFMultipartFormData *formData = [[[AFMultipartFormData alloc] initWithURLRequest:request stringEncoding:self.stringEncoding] autorelease];
for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) {
NSData *data = nil;
if ([component.value isKindOfClass:[NSData class]]) {
@ -505,7 +517,6 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
[formData appendPartWithFormData:data name:[component.key description]];
}
}
}
if (block) {
block(formData);
@ -700,18 +711,23 @@ static inline NSString * AFMultipartFormFinalBoundary() {
return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, kAFMultipartFormBoundary, kAFMultipartFormCRLF];
}
@interface AFMultipartFormData ()
#pragma mark --
#pragma mark AFStreamingMultipartFormData
@interface AFStreamingMultipartFormData () {
AFMultipartBodyStream * bodyStream;
}
@property (readwrite, nonatomic, retain) NSMutableURLRequest *request;
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
@property (readwrite, nonatomic, retain) NSOutputStream *outputStream;
@property (readwrite, nonatomic, copy) NSString *temporaryFilePath;
@end
@implementation AFMultipartFormData
@implementation AFStreamingMultipartFormData
@synthesize request = _request;
@synthesize stringEncoding = _stringEncoding;
@synthesize outputStream = _outputStream;
@synthesize temporaryFilePath = _temporaryFilePath;
- (id)initWithURLRequest:(NSMutableURLRequest *)request
stringEncoding:(NSStringEncoding)encoding
@ -720,172 +736,432 @@ static inline NSString * AFMultipartFormFinalBoundary() {
if (!self) {
return nil;
}
self.request = request;
self.stringEncoding = encoding;
self.temporaryFilePath = [AFMultipartTemporaryFileDirectoryPath() stringByAppendingPathComponent:[NSString stringWithFormat:@"%u", [[self.request URL] hash]]];
self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.temporaryFilePath append:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[self.outputStream scheduleInRunLoop:runLoop forMode:NSRunLoopCommonModes];
[self.outputStream open];
bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];
return self;
}
- (void)dealloc {
[_request release];
if (_outputStream) {
[_outputStream close];
[_outputStream release];
_outputStream = nil;
}
[_temporaryFilePath release];
[bodyStream release];
[super dealloc];
}
-(void)appendPartWithFormData:(NSData *)data name:(NSString *)name {
[bodyStream addFormData:data name:name];
}
-(BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error {
return [bodyStream addFileFromURL:fileURL name:name error:error];
}
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
// Close the stream and return the original request if no data has been written
if ([[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] integerValue] == 0) {
[self.outputStream close];
//return the original request if no data has been added
if ([bodyStream empty]) {
return self.request;
}
[self appendData:[AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]];
[self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"];
[self.request setValue:[[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] stringValue] forHTTPHeaderField:@"Content-Length"];
[self.outputStream close];
[self.request setHTTPBodyStream:[NSInputStream inputStreamWithFileAtPath:self.temporaryFilePath]];
[self.request setValue:[NSString stringWithFormat:@"%d",[bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];
[self.request setHTTPBodyStream:bodyStream];
return self.request;
}
#pragma mark - AFMultipartFormData
-(void)appendData:(NSData *)data {
- (void)appendBoundary {
if ([[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] integerValue] == 0) {
[self appendString:AFMultipartFormInitialBoundary()];
} else {
[self appendString:AFMultipartFormEncapsulationBoundary()];
}
}
- (void)appendPartWithHeaders:(NSDictionary *)headers
body:(NSData *)body
{
[self appendBoundary];
@end
for (NSString *field in [headers allKeys]) {
[self appendString:[NSString stringWithFormat:@"%@: %@%@", field, [headers valueForKey:field], kAFMultipartFormCRLF]];
#pragma mark --
#pragma mark AFMultipartBodyStream
@interface AFMultipartBodyStream () {
//all undocumented nsstream/nsinputstream/CFReadStream bull shit
CFReadStreamClientCallBack copiedCallback;
CFStreamClientContext copiedContext;
CFOptionFlags requestedEvents;
NSStreamStatus streamStatus;
id <NSStreamDelegate> delegate;
NSMutableArray * fileNames;
NSMutableDictionary * fileURLs;
NSMutableDictionary * fileHeaders;
NSMutableArray * formNames;
NSMutableDictionary * formDatas;
NSMutableDictionary * formHeaders;
NSUInteger readElementCursor;
NSUInteger readOffsetCursor;
NSUInteger readHeaderOffsetCursor;
NSInputStream * currentFileStream;
NSStringEncoding stringEncoding;
}
[self appendString:kAFMultipartFormCRLF];
[self appendData:body];
@end
@implementation AFMultipartBodyStream
-(id)init {
self = [super init];
fileNames = [[NSMutableArray alloc] init];
fileURLs = [[NSMutableDictionary alloc] init];
fileHeaders = [[NSMutableDictionary alloc] init];
formNames = [[NSMutableArray alloc] init];
formDatas = [[NSMutableDictionary alloc] init];
formHeaders = [[NSMutableDictionary alloc] init];
currentFileStream = NULL;
stringEncoding = NSUTF8StringEncoding;
streamStatus = NSStreamStatusNotOpen;
[self resetCursors];
[self setDelegate:self];
return self;
}
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name
{
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];
[self appendPartWithHeaders:mutableHeaders body:data];
-(id)initWithStringEncoding:(NSStringEncoding)encoding {
self = [self init];
stringEncoding = encoding;
return self;
}
- (NSMutableDictionary *)fileHeadersWithName:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType {
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
return mutableHeaders;
-(void)resetCursors {
readElementCursor = 0;
readOffsetCursor = 0;
readHeaderOffsetCursor = 0;
}
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
{
[self appendPartWithHeaders:[self fileHeadersWithName:name fileName:fileName mimeType:mimeType] body:data];
-(void)dealloc {
[fileNames release];
[fileURLs release];
[fileHeaders release];
[formNames release];
[formDatas release];
[formHeaders release];
if (currentFileStream) {
[currentFileStream close];
[currentFileStream release];
currentFileStream = NULL;
}
[super dealloc];
}
- (void)appendPartWithStreamingURL:(NSURL *)streamingURL
name:(NSString *)name
mimeType:(NSString *)mimeType
{
NSString * fileName = [[streamingURL pathComponents] objectAtIndex:([[streamingURL pathComponents] count] - 1)];
[self appendPartWithHeaders:[self fileHeadersWithName:name fileName:fileName mimeType:mimeType] body:nil];
NSInputStream * inputStream = [NSInputStream inputStreamWithURL:streamingURL];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[inputStream open];
void * dataBuffer = malloc(kAFStreamToStreamBufferSize);
NSInteger bytesRead = [inputStream read:dataBuffer maxLength:kAFStreamToStreamBufferSize];
while (bytesRead > 0) {
NSData * tempData = [NSData dataWithBytesNoCopy:dataBuffer length:bytesRead freeWhenDone:NO];
[self appendData:tempData];
bytesRead = [inputStream read:dataBuffer maxLength:kAFStreamToStreamBufferSize];
-(BOOL)empty {
if (([fileURLs count] + [formDatas count]) == 0)
return YES;
else
return NO;
}
[inputStream close];
free(dataBuffer);
}
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError **)error
{
if (![fileURL isFileURL]) {
-(BOOL)addFileFromURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error {
assert([self streamStatus] == NSStreamStatusNotOpen);
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:fileURL forKey:NSURLErrorFailingURLErrorKey];
if (![fileURL isFileURL]) {
[userInfo setValue:NSLocalizedString(@"Expected URL to be a file URL", nil) forKey:NSLocalizedFailureReasonErrorKey];
if (error != NULL) {
*error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadURL userInfo:userInfo] autorelease];
}
return NO;
}
if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) {
[userInfo setValue:NSLocalizedString(@"File URL not reachable.", nil) forKey:NSLocalizedFailureReasonErrorKey];
if (error != NULL) {
*error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadURL userInfo:userInfo] autorelease];
}
return NO;
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[fileNames addObject:name];
[fileURLs setObject:fileURL forKey:name];
[self generateHeaders];
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error];
return YES;
}
if (data && response) {
[self appendPartWithFileData:data name:name fileName:[response suggestedFilename] mimeType:[response MIMEType]];
-(void)addFormData:(NSData *)data name:(NSString *)name {
assert([self streamStatus] == NSStreamStatusNotOpen);
[formNames addObject:name];
[formDatas setObject:data forKey:name];
[self generateHeaders];
}
-(void)generateHeaders {
[formHeaders removeAllObjects];
for (NSString * formName in formNames) {
[formHeaders setObject:[self headersDataForForm:formName] forKey:formName];
}
[fileHeaders removeAllObjects];
for (NSString * fileName in fileNames) {
[fileHeaders setObject:[self headersDataForFile:fileName] forKey:fileName];
}
}
-(NSString *)stringForHeaders:(NSDictionary *)headers {
NSMutableString * headerString = [NSMutableString string];
for (NSString *field in [headers allKeys]) {
[headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [headers valueForKey:field], kAFMultipartFormCRLF]];
}
return [NSString stringWithString:headerString];
}
-(NSData *)headersDataForDict:(NSDictionary *)headersDict {
NSMutableString * headerString = [NSMutableString string];
if ([formHeaders count] == 0 && [fileHeaders count] == 0) {
[headerString appendString:AFMultipartFormInitialBoundary()];
} else {
[headerString appendString:AFMultipartFormEncapsulationBoundary()];
}
[headerString appendString:[self stringForHeaders:headersDict]];
[headerString appendString:kAFMultipartFormCRLF];
return [headerString dataUsingEncoding:stringEncoding];
}
-(NSData *)headersDataForFile:(NSString *)name {
NSURL * fileURL = [fileURLs objectForKey:name];
NSString * fileName = [[fileURL pathComponents] objectAtIndex:([[fileURL pathComponents] count] - 1)];
NSMutableDictionary * mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[fileURL pathExtension], NULL);
NSString * MIMEType = [(NSString*) UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType) autorelease];
CFRelease(UTI);
[mutableHeaders setValue:MIMEType forKey:@"Content-Type"];
return [self headersDataForDict:mutableHeaders];
}
-(NSData *)headersDataForForm:(NSString *)name {
return [self headersDataForDict:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"]];
}
-(NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)len offsetCursor:(NSUInteger*)offsetCursorPtr {
NSUInteger bytesAvailable = [data length] - *offsetCursorPtr;
if (len > bytesAvailable) {
[data getBytes:buffer range:NSMakeRange(*offsetCursorPtr, bytesAvailable)];
*offsetCursorPtr += bytesAvailable;
return bytesAvailable;
} else {
[data getBytes:buffer range:NSMakeRange(*offsetCursorPtr, len)];
*offsetCursorPtr += len;
return len;
}
}
-(void)nextElement {
readOffsetCursor = 0;
readHeaderOffsetCursor = 0;
readElementCursor += 1;
if (currentFileStream) {
[currentFileStream close];
[currentFileStream release];
currentFileStream = NULL;
}
}
-(NSUInteger)contentLength {
NSUInteger total = 0;
for (NSString * formName in formNames) {
total += [[formHeaders objectForKey:formName] length];
total += [[formDatas objectForKey:formName] length];
}
for (NSString * fileName in fileNames) {
NSError * error;
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[[fileURLs objectForKey:fileName] path] error:&error];
total += [[fileHeaders objectForKey:fileName] length];
total += [[fileAttributes objectForKey:NSFileSize] longValue];
}
total += [[self finalBoundaryData] length];
return total;
}
-(NSUInteger)totalElements {
return [formDatas count] + [fileURLs count];
}
-(NSData *)finalBoundaryData {
return [AFMultipartFormFinalBoundary() dataUsingEncoding:stringEncoding];
}
#pragma mark - NSStream subclass overrides
-(void)open {
streamStatus = NSStreamStatusOpen;
}
-(void)close {
streamStatus = NSStreamStatusClosed;
[self resetCursors];
}
- (id <NSStreamDelegate> )delegate {
return delegate;
}
- (void)setDelegate:(id<NSStreamDelegate>)aDelegate {
if (aDelegate == nil) {
delegate = self;
}
else {
delegate = aDelegate;
}
}
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode {
}
- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode {
}
- (id)propertyForKey:(NSString *)key {
return nil;
}
- (BOOL)setProperty:(id)property forKey:(NSString *)key {
return NO;
}
- (NSStreamStatus)streamStatus {
return streamStatus;
}
- (NSError *)streamError {
return nil;
}
#pragma mark - NSInputStream subclass overrides
-(NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
assert ([self streamStatus] == NSStreamStatusOpen);
NSUInteger bytesRead = 0;
NSUInteger readFileCursor = (readElementCursor - [formNames count]);
if (readElementCursor < [formNames count]) {
//reading from formDatas
NSString * currentFormName = [formNames objectAtIndex:readElementCursor];
NSData * currentData = [formDatas objectForKey:currentFormName];
NSData * headersData = [formHeaders objectForKey:currentFormName];
if (readHeaderOffsetCursor < [headersData length]) {
bytesRead = [self readData:headersData intoBuffer:buffer maxLength:len offsetCursor:&readHeaderOffsetCursor];
} else {
bytesRead = [self readData:currentData intoBuffer:buffer maxLength:len offsetCursor:&readOffsetCursor];
}
if (readOffsetCursor == [currentData length]) {
[self nextElement];
}
}
else if (readElementCursor >= [formNames count] && readFileCursor < [fileNames count]) {
//reading from files
NSString * currentFileName = [fileNames objectAtIndex:readFileCursor];
NSURL * currentFileURL = [fileURLs objectForKey:currentFileName];
NSData * headersData = [fileHeaders objectForKey:currentFileName];
assert(headersData != NULL);
if (readHeaderOffsetCursor < [headersData length]) {
bytesRead = [self readData:headersData intoBuffer:buffer maxLength:len offsetCursor:&readHeaderOffsetCursor];
} else {
if (!currentFileStream) {
currentFileStream = [[NSInputStream inputStreamWithURL:currentFileURL] retain];
currentFileStream.delegate = self;
[currentFileStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[currentFileStream open];
}
bytesRead = [currentFileStream read:buffer maxLength:len];
if (![currentFileStream hasBytesAvailable] || bytesRead == 0) {
[self nextElement];
}
}
}
else {
//add final boundary
bytesRead = [self readData:[self finalBoundaryData] intoBuffer:buffer maxLength:len offsetCursor:&readOffsetCursor];
if (readOffsetCursor == [[self finalBoundaryData] length]) {
[self nextElement];
}
}
if (bytesRead < len && readElementCursor <= [self totalElements]) {
//recurse to fill out the buffer, this is critical if the above read operations produce a zero length buffer for some reason (empty form data, end of file) because returning zero from this method will result in the stream being closed.
bytesRead += [self read:buffer+bytesRead maxLength:len-bytesRead];
}
//doesn't seem to make a diff if we do the callbacks or not for the HTTP request use anyway,
//and it sometimes crashes if the callback reciever or context have been released.
// if (CFReadStreamGetStatus((CFReadStreamRef)self) == kCFStreamStatusOpen) {
// double delayInSeconds = 0;
// dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
// dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// if (copiedCallback && (requestedEvents & kCFStreamEventHasBytesAvailable)) {
// NSLog(@"callback kCFStreamEventHasBytesAvailable... bytes read: %d", bytesRead);
// copiedCallback((CFReadStreamRef)self, kCFStreamEventHasBytesAvailable, &copiedContext);
// }
// });
// }
assert(bytesRead > 0); //really should never return zero from this call
return bytesRead;
}
-(BOOL)hasBytesAvailable {
if ([self totalElements] == 0) {
return NO;
} else if (readElementCursor < ([self totalElements] + 1)) {
return YES;
} else {
return NO;
}
}
- (void)appendString:(NSString *)string {
[self appendData:[string dataUsingEncoding:self.stringEncoding]];
-(BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len {
return NO;
}
- (void)appendData:(NSData *)data {
if ([data length] == 0) {
return;
#pragma mark - Undocumented CFReadStream bridged methods
- (void)_scheduleInCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode {
}
if ([self.outputStream hasSpaceAvailable]) {
const uint8_t *dataBuffer = (uint8_t *) [data bytes];
[self.outputStream write:&dataBuffer[0] maxLength:[data length]];
} else {
NSLog(@"Failed to append to outputStream!");
- (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);
}
copiedCallback((CFReadStreamRef)self, kCFStreamEventHasBytesAvailable, &copiedContext);
}
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 {
}
@end

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
50ABD6ED159FC2CE001BE42C /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ABD6EC159FC2CE001BE42C /* MobileCoreServices.framework */; };
F8129C7415910C37009BFE23 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F8129C7215910C37009BFE23 /* AppDelegate.m */; };
F8D0701B14310F4A00653FD3 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E213957DF700DB05C8 /* SystemConfiguration.framework */; };
F8D0701C14310F4F00653FD3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E013957DF100DB05C8 /* Security.framework */; };
@ -39,6 +40,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
50ABD6EC159FC2CE001BE42C /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
F8129C3815910830009BFE23 /* Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = SOURCE_ROOT; };
F8129C7215910C37009BFE23 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = SOURCE_ROOT; };
F8129C7315910C37009BFE23 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; };
@ -95,6 +97,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
50ABD6ED159FC2CE001BE42C /* MobileCoreServices.framework in Frameworks */,
F8E469651395739D00DB05C8 /* UIKit.framework in Frameworks */,
F8E469671395739D00DB05C8 /* Foundation.framework in Frameworks */,
F8E469691395739D00DB05C8 /* CoreGraphics.framework in Frameworks */,
@ -142,6 +145,7 @@
F8E469551395739C00DB05C8 = {
isa = PBXGroup;
children = (
50ABD6EC159FC2CE001BE42C /* MobileCoreServices.framework */,
F8E469B71395759C00DB05C8 /* Networking Extensions */,
F8E4696A1395739D00DB05C8 /* Classes */,
F8E469ED1395812A00DB05C8 /* Images */,