1// Copyright © 2019 650 Industries. All rights reserved. 2 3#import <EXUpdates/EXUpdatesMultipartStreamReader.h> 4 5#define CRLF @"\r\n" 6 7@implementation EXUpdatesMultipartStreamReader { 8 __strong NSInputStream *_stream; 9 __strong NSString *_boundary; 10} 11 12- (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary 13{ 14 if (self = [super init]) { 15 _stream = stream; 16 _boundary = boundary; 17 } 18 return self; 19} 20 21- (NSDictionary<NSString *, NSString *> *)parseHeaders:(NSData *)data 22{ 23 NSMutableDictionary *headers = [NSMutableDictionary new]; 24 NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 25 NSArray<NSString *> *lines = [text componentsSeparatedByString:CRLF]; 26 for (NSString *line in lines) { 27 NSUInteger location = [line rangeOfString:@":"].location; 28 if (location == NSNotFound) { 29 continue; 30 } 31 NSString *key = [line substringToIndex:location]; 32 NSString *value = [[line substringFromIndex:location + 1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 33 [headers setValue:value forKey:key]; 34 } 35 return headers; 36} 37 38- (void)emitChunk:(NSData *)data headers:(NSDictionary *)headers callback:(RCTMultipartCallback)callback done:(BOOL)done 39{ 40 NSData *marker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding]; 41 NSRange range = [data rangeOfData:marker options:0 range:NSMakeRange(0, data.length)]; 42 if (range.location == NSNotFound) { 43 callback(nil, data, done); 44 } else if (headers != nil) { 45 // If headers were parsed already just use that to avoid doing it twice. 46 NSInteger bodyStart = range.location + marker.length; 47 NSData *bodyData = [data subdataWithRange:NSMakeRange(bodyStart, data.length - bodyStart)]; 48 callback(headers, bodyData, done); 49 } else { 50 NSData *headersData = [data subdataWithRange:NSMakeRange(0, range.location)]; 51 NSInteger bodyStart = range.location + marker.length; 52 NSData *bodyData = [data subdataWithRange:NSMakeRange(bodyStart, data.length - bodyStart)]; 53 callback([self parseHeaders:headersData], bodyData, done); 54 } 55} 56 57- (BOOL)readAllPartsWithCompletionCallback:(RCTMultipartCallback)callback 58{ 59 NSInteger chunkStart = 0; 60 NSInteger bytesSeen = 0; 61 62 // first delimiter doesn't necessarily need to be preceded by CRLF (boundary can be first thing in body) 63 NSData *firstDelimiter = [[NSString stringWithFormat:@"--%@%@", _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding]; 64 NSData *restDelimiter = [[NSString stringWithFormat:@"%@--%@%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding]; 65 NSData *delimiter = firstDelimiter; 66 67 NSData *closeDelimiter = [[NSString stringWithFormat:@"%@--%@--%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding]; 68 NSMutableData *content = [[NSMutableData alloc] initWithCapacity:1]; 69 NSDictionary *currentHeaders = nil; 70 NSUInteger currentHeadersLength = 0; 71 72 const NSUInteger bufferLen = 4 * 1024; 73 uint8_t buffer[bufferLen]; 74 75 [_stream open]; 76 while (true) { 77 BOOL isCloseDelimiter = NO; 78 // Search only a subset of chunk that we haven't seen before + few bytes 79 // to allow for the edge case when the delimiter is cut by read call 80 NSInteger searchStart = MAX(bytesSeen - (NSInteger)closeDelimiter.length, chunkStart); 81 NSRange remainingBufferRange = NSMakeRange(searchStart, content.length - searchStart); 82 83 // Check for delimiters. 84 NSRange range = [content rangeOfData:delimiter options:0 range:remainingBufferRange]; 85 if (range.location == NSNotFound) { 86 isCloseDelimiter = YES; 87 range = [content rangeOfData:closeDelimiter options:0 range:remainingBufferRange]; 88 } 89 90 if (range.location == NSNotFound) { 91 if (currentHeaders == nil) { 92 // Check for the headers delimiter. 93 NSData *headersMarker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding]; 94 NSRange headersRange = [content rangeOfData:headersMarker options:0 range:remainingBufferRange]; 95 if (headersRange.location != NSNotFound) { 96 NSData *headersData = [content subdataWithRange:NSMakeRange(chunkStart, headersRange.location - chunkStart)]; 97 currentHeadersLength = headersData.length; 98 currentHeaders = [self parseHeaders:headersData]; 99 } 100 } 101 102 bytesSeen = content.length; 103 NSInteger bytesRead = [_stream read:buffer maxLength:bufferLen]; 104 if (bytesRead <= 0 || _stream.streamError) { 105 [_stream close]; 106 return NO; 107 } 108 [content appendBytes:buffer length:bytesRead]; 109 continue; 110 } 111 112 NSInteger chunkEnd = range.location; 113 NSInteger length = chunkEnd - chunkStart; 114 bytesSeen = chunkEnd; 115 116 // Ignore preamble 117 if (chunkStart > 0) { 118 NSData *chunk = [content subdataWithRange:NSMakeRange(chunkStart, length)]; 119 [self emitChunk:chunk headers:currentHeaders callback:callback done:isCloseDelimiter]; 120 currentHeaders = nil; 121 currentHeadersLength = 0; 122 } 123 124 if (isCloseDelimiter) { 125 [_stream close]; 126 return YES; 127 } 128 129 chunkStart = chunkEnd + delimiter.length; 130 131 if (delimiter == firstDelimiter) { 132 delimiter = restDelimiter; 133 } 134 } 135} 136 137@end 138