diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 4c72360..b2f1b52 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -801,8 +801,8 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; maxLength:(NSUInteger)length; @end -@interface AFMultipartBodyStreamProvider : NSObject -@property (nonatomic, assign) NSUInteger bufferLength; +@interface AFMultipartBodyStream : NSInputStream +@property (nonatomic, assign) NSUInteger numberOfBytesInPacket; @property (nonatomic, assign) NSTimeInterval delay; @property (nonatomic, strong) NSInputStream *inputStream; @property (nonatomic, readonly) unsigned long long contentLength; @@ -817,7 +817,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; @interface AFStreamingMultipartFormData () @property (readwrite, nonatomic, copy) NSMutableURLRequest *request; -@property (readwrite, nonatomic, strong) AFMultipartBodyStreamProvider *bodyStream; +@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream; @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; @end @@ -836,7 +836,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; self.request = urlRequest; self.stringEncoding = encoding; - self.bodyStream = [[AFMultipartBodyStreamProvider alloc] initWithStringEncoding:encoding]; + self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding]; return self; } @@ -968,7 +968,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; - (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay { - self.bodyStream.bufferLength = numberOfBytes; + self.bodyStream.numberOfBytesInPacket = numberOfBytes; self.bodyStream.delay = delay; } @@ -982,7 +982,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"]; [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"]; - [self.request setHTTPBodyStream:self.bodyStream.inputStream]; + [self.request setHTTPBodyStream:self.bodyStream]; return self.request; } @@ -991,7 +991,9 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; #pragma mark - -@interface AFMultipartBodyStreamProvider () +@interface AFMultipartBodyStream () +@property (nonatomic, assign) NSStreamStatus streamStatus; +@property (nonatomic, strong) NSError *streamError; @property (nonatomic, assign) NSStringEncoding stringEncoding; @property (nonatomic, strong) NSMutableArray *HTTPBodyParts; @property (nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator; @@ -1000,13 +1002,9 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; @property (nonatomic, strong) NSMutableData *buffer; @end -static const NSUInteger AFMultipartBodyStreamProviderDefaultBufferLength = 4096; - -@implementation AFMultipartBodyStreamProvider { -@private - // Workaround for stream delegates being weakly referenced, but otherwise unowned - __strong id _self; -} +@implementation AFMultipartBodyStream +@synthesize streamStatus = _streamStatus; +@synthesize streamError = _streamError; @synthesize stringEncoding = _stringEncoding; @synthesize HTTPBodyParts = _HTTPBodyParts; @synthesize HTTPBodyPartEnumerator = _HTTPBodyPartEnumerator; @@ -1014,7 +1012,7 @@ static const NSUInteger AFMultipartBodyStreamProviderDefaultBufferLength = 4096; @synthesize inputStream = _inputStream; @synthesize outputStream = _outputStream; @synthesize buffer = _buffer; -@synthesize bufferLength = _numberOfBytesInPacket; +@synthesize numberOfBytesInPacket = _numberOfBytesInPacket; @synthesize delay = _delay; - (id)initWithStringEncoding:(NSStringEncoding)encoding { @@ -1025,16 +1023,9 @@ static const NSUInteger AFMultipartBodyStreamProviderDefaultBufferLength = 4096; self.stringEncoding = encoding; self.HTTPBodyParts = [NSMutableArray array]; - self.bufferLength = NSIntegerMax; - - self.buffer = [[NSMutableData alloc] init]; - self.bufferLength = AFMultipartBodyStreamProviderDefaultBufferLength; - - return self; -} + self.numberOfBytesInPacket = NSIntegerMax; -- (void)dealloc { - _outputStream.delegate = nil; + return self; } - (void)setInitialAndFinalBoundaries { @@ -1053,116 +1044,80 @@ static const NSUInteger AFMultipartBodyStreamProviderDefaultBufferLength = 4096; [self.HTTPBodyParts addObject:bodyPart]; } -- (NSInputStream *)inputStream { - if (_inputStream == nil) { - CFReadStreamRef readStream; - CFWriteStreamRef writeStream; - CFStreamCreateBoundPair(NULL, &readStream, &writeStream, (NSInteger)self.bufferLength); - _inputStream = CFBridgingRelease(readStream); - _outputStream = CFBridgingRelease(writeStream); - - _outputStream.delegate = self; - if ([NSThread isMainThread]) { - [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; - } else { - dispatch_sync(dispatch_get_main_queue(), ^{ - [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; - }); - } - [_outputStream open]; - - _self = self; - } - - return _inputStream; -} - - (BOOL)isEmpty { return [self.HTTPBodyParts count] == 0; } -#pragma mark - NSStreamDelegate +#pragma mark - NSInputStream - -// This retry works around a nasty problem in which mutli-part uploads will fail due to the stream delegate being sent a `NSStreamEventHasSpaceAvailable` event before the input stream has finished opening. -// This workaround simply replays the event after allowing the run-loop to cycle, providing enough time for the input stream to finish opening. It appears that this bug is in the CFNetwork layer. -// See: https://github.com/AFNetworking/AFNetworking/issues/948 -- (void)retryWrite:(NSStream *)stream { - [self stream:stream handleEvent:NSStreamEventHasSpaceAvailable]; -} - -- (void)stream:(NSStream *)stream - handleEvent:(NSStreamEvent)eventCode { - if (eventCode & NSStreamEventHasSpaceAvailable) { - if (self.inputStream.streamStatus < NSStreamStatusOpen) { - [self performSelector:@selector(retryWrite:) withObject:stream afterDelay:0.1]; - } else { - [self handleOutputStreamSpaceAvailable]; - } +- (NSInteger)read:(uint8_t *)buffer + maxLength:(NSUInteger)length +{ + if ([self streamStatus] == NSStreamStatusClosed) { + return 0; } -} + NSInteger bytesRead = 0; -- (void)handleOutputStreamSpaceAvailable { - while ([_outputStream hasSpaceAvailable]) { - if ([_buffer length] > 0) { - NSInteger numberOfBytesWritten = [_outputStream write:(uint8_t const *)[_buffer bytes] maxLength:[_buffer length]]; - if (numberOfBytesWritten < 0) { - [self close]; - return; + while ((NSUInteger)bytesRead < MIN(length, self.numberOfBytesInPacket)) { + if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { + if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { + break; } - - [_buffer replaceBytesInRange:NSMakeRange(0, (NSUInteger)numberOfBytesWritten) withBytes:NULL length:0]; } else { - if (!self.currentHTTPBodyPart) { - if (!self.HTTPBodyPartEnumerator) { - self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator]; - } - self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject]; - } - - if (!self.currentHTTPBodyPart) { - [self close]; - return; - } - - [_buffer setLength:self.bufferLength]; - - NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:(uint8_t *)[_buffer mutableBytes] maxLength:[_buffer length]]; - if (numberOfBytesRead < 0) { - [self close]; - return; - } - - [_buffer setLength:(NSUInteger)numberOfBytesRead]; - - if (numberOfBytesRead == 0) { - self.currentHTTPBodyPart = nil; - } - + bytesRead += [self.currentHTTPBodyPart read:&buffer[bytesRead] maxLength:(length - (NSUInteger)bytesRead)]; if (self.delay > 0.0f) { [NSThread sleepForTimeInterval:self.delay]; } } } + return bytesRead; +} + +- (BOOL)getBuffer:(__unused uint8_t **)buffer + length:(__unused NSUInteger *)len +{ + return NO; +} + +- (BOOL)hasBytesAvailable { + return [self streamStatus] == NSStreamStatusOpen; +} + +#pragma mark - NSStream + +- (void)open { + if (self.streamStatus == NSStreamStatusOpen) { + return; + } + + self.streamStatus = NSStreamStatusOpen; + + [self setInitialAndFinalBoundaries]; + self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator]; } - (void)close { - NSOutputStream *outputStream = self.outputStream; - - [outputStream close]; - outputStream.delegate = nil; - - // Workaround for a race condition in CFStream _CFStreamCopyRunLoopsAndModes. This outputstream needs to be retained just a little longer. - // See: https://github.com/AFNetworking/AFNetworking/issues/907 - NSTimeInterval delay = 2.0; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^{ - outputStream.delegate = nil; - }); - - _self = nil; + self.streamStatus = NSStreamStatusClosed; } +- (id)propertyForKey:(__unused NSString *)key { + return nil; +} + +- (BOOL)setProperty:(__unused id)property + forKey:(__unused NSString *)key +{ + return NO; +} + +- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop + forMode:(__unused NSString *)mode +{} + +- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop + forMode:(__unused NSString *)mode +{} + - (unsigned long long)contentLength { unsigned long long length = 0; for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { @@ -1172,10 +1127,26 @@ static const NSUInteger AFMultipartBodyStreamProviderDefaultBufferLength = 4096; return length; } +#pragma mark - Undocumented CFReadStream Bridged Methods + +- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop + forMode:(__unused CFStringRef)aMode +{} + +- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop + forMode:(__unused CFStringRef)aMode +{} + +- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags + callback:(__unused CFReadStreamClientCallBack)inCallback + context:(__unused CFStreamClientContext *)inContext { + return NO; +} + #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { - AFMultipartBodyStreamProvider *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding]; +-(id)copyWithZone:(NSZone *)zone { + AFMultipartBodyStream *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding]; for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { [bodyStreamCopy appendHTTPBodyPart:[bodyPart copy]]; @@ -1191,12 +1162,10 @@ static const NSUInteger AFMultipartBodyStreamProviderDefaultBufferLength = 4096; #pragma mark - typedef enum { - AFInitialPhase = 0, AFEncapsulationBoundaryPhase = 1, AFHeaderPhase = 2, AFBodyPhase = 3, AFFinalBoundaryPhase = 4, - AFCompletedPhase = 5, } AFHTTPBodyPartReadPhase; @interface AFHTTPBodyPart () { @@ -1301,7 +1270,6 @@ typedef enum { return NO; } #pragma clang diagnostic pop - } - (NSInteger)read:(uint8_t *)buffer @@ -1365,9 +1333,6 @@ typedef enum { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcovered-switch-default" switch (_phase) { - case AFInitialPhase: - _phase = AFEncapsulationBoundaryPhase; - break; case AFEncapsulationBoundaryPhase: _phase = AFHeaderPhase; break; @@ -1381,9 +1346,8 @@ typedef enum { _phase = AFFinalBoundaryPhase; break; case AFFinalBoundaryPhase: - case AFCompletedPhase: default: - _phase = AFCompletedPhase; + _phase = AFEncapsulationBoundaryPhase; break; } _phaseReadOffset = 0;