-
Notifications
You must be signed in to change notification settings - Fork 24.4k
/
RCTMultipartStreamReader.m
112 lines (96 loc) · 3.81 KB
/
RCTMultipartStreamReader.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTMultipartStreamReader.h"
#define CRLF @"\r\n"
@implementation RCTMultipartStreamReader {
__strong NSInputStream *_stream;
__strong NSString *_boundary;
}
- (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary
{
if (self = [super init]) {
_stream = stream;
_boundary = boundary;
}
return self;
}
- (NSDictionary *)parseHeaders:(NSData *)data
{
NSMutableDictionary *headers = [NSMutableDictionary new];
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray<NSString *> *lines = [text componentsSeparatedByString:CRLF];
for (NSString *line in lines) {
NSUInteger location = [line rangeOfString:@":"].location;
if (location == NSNotFound) {
continue;
}
NSString *key = [line substringToIndex:location];
NSString *value = [[line substringFromIndex:location + 1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[headers setValue:value forKey:key];
}
return headers;
}
- (void)emitChunk:(NSData *)data callback:(RCTMultipartCallback)callback done:(BOOL)done
{
NSData *marker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding];
NSRange range = [data rangeOfData:marker options:0 range:NSMakeRange(0, data.length)];
if (range.location == NSNotFound) {
callback(nil, data, done);
} else {
NSData *headersData = [data subdataWithRange:NSMakeRange(0, range.location)];
NSInteger bodyStart = range.location + marker.length;
NSData *bodyData = [data subdataWithRange:NSMakeRange(bodyStart, data.length - bodyStart)];
callback([self parseHeaders:headersData], bodyData, done);
}
}
- (BOOL)readAllParts:(RCTMultipartCallback)callback
{
NSInteger chunkStart = 0;
NSInteger bytesSeen = 0;
NSData *delimiter = [[NSString stringWithFormat:@"%@--%@%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
NSData *closeDelimiter = [[NSString stringWithFormat:@"%@--%@--%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *content = [[NSMutableData alloc] initWithCapacity:1];
const NSUInteger bufferLen = 4 * 1024;
uint8_t buffer[bufferLen];
[_stream open];
while (true) {
BOOL isCloseDelimiter = NO;
// Search only a subset of chunk that we haven't seen before + few bytes
// to allow for the edge case when the delimiter is cut by read call
NSInteger searchStart = MAX(bytesSeen - (NSInteger)closeDelimiter.length, chunkStart);
NSRange remainingBufferRange = NSMakeRange(searchStart, content.length - searchStart);
NSRange range = [content rangeOfData:delimiter options:0 range:remainingBufferRange];
if (range.location == NSNotFound) {
isCloseDelimiter = YES;
range = [content rangeOfData:closeDelimiter options:0 range:remainingBufferRange];
}
if (range.location == NSNotFound) {
bytesSeen = content.length;
NSInteger bytesRead = [_stream read:buffer maxLength:bufferLen];
if (bytesRead <= 0 || _stream.streamError) {
return NO;
}
[content appendBytes:buffer length:bytesRead];
continue;
}
NSInteger chunkEnd = range.location;
NSInteger length = chunkEnd - chunkStart;
bytesSeen = chunkEnd;
// Ignore preamble
if (chunkStart > 0) {
NSData *chunk = [content subdataWithRange:NSMakeRange(chunkStart, length)];
[self emitChunk:chunk callback:callback done:isCloseDelimiter];
}
if (isCloseDelimiter) {
return YES;
}
chunkStart = chunkEnd + delimiter.length;
}
}
@end