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