RCTMultipartStreamReader.m 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTMultipartStreamReader.h"
  8. #import <QuartzCore/CAAnimation.h>
  9. #define CRLF @"\r\n"
  10. @implementation RCTMultipartStreamReader {
  11. __strong NSInputStream *_stream;
  12. __strong NSString *_boundary;
  13. CFTimeInterval _lastDownloadProgress;
  14. }
  15. - (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary
  16. {
  17. if (self = [super init]) {
  18. _stream = stream;
  19. _boundary = boundary;
  20. _lastDownloadProgress = CACurrentMediaTime();
  21. }
  22. return self;
  23. }
  24. - (NSDictionary *)parseHeaders:(NSData *)data
  25. {
  26. NSMutableDictionary *headers = [NSMutableDictionary new];
  27. NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  28. NSArray<NSString *> *lines = [text componentsSeparatedByString:CRLF];
  29. for (NSString *line in lines) {
  30. NSUInteger location = [line rangeOfString:@":"].location;
  31. if (location == NSNotFound) {
  32. continue;
  33. }
  34. NSString *key = [line substringToIndex:location];
  35. NSString *value = [[line substringFromIndex:location + 1]
  36. stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  37. [headers setValue:value forKey:key];
  38. }
  39. return headers;
  40. }
  41. - (void)emitChunk:(NSData *)data headers:(NSDictionary *)headers callback:(RCTMultipartCallback)callback done:(BOOL)done
  42. {
  43. NSData *marker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding];
  44. NSRange range = [data rangeOfData:marker options:0 range:NSMakeRange(0, data.length)];
  45. if (range.location == NSNotFound) {
  46. callback(nil, data, done);
  47. } else if (headers != nil) {
  48. // If headers were parsed already just use that to avoid doing it twice.
  49. NSInteger bodyStart = range.location + marker.length;
  50. NSData *bodyData = [data subdataWithRange:NSMakeRange(bodyStart, data.length - bodyStart)];
  51. callback(headers, bodyData, done);
  52. } else {
  53. NSData *headersData = [data subdataWithRange:NSMakeRange(0, range.location)];
  54. NSInteger bodyStart = range.location + marker.length;
  55. NSData *bodyData = [data subdataWithRange:NSMakeRange(bodyStart, data.length - bodyStart)];
  56. callback([self parseHeaders:headersData], bodyData, done);
  57. }
  58. }
  59. - (void)emitProgress:(NSDictionary *)headers
  60. contentLength:(NSUInteger)contentLength
  61. final:(BOOL)final
  62. callback:(RCTMultipartProgressCallback)callback
  63. {
  64. if (headers == nil) {
  65. return;
  66. }
  67. // Throttle progress events so we don't send more that around 60 per second.
  68. CFTimeInterval currentTime = CACurrentMediaTime();
  69. NSInteger headersContentLength = headers[@"Content-Length"] != nil ? [headers[@"Content-Length"] integerValue] : 0;
  70. if (callback && (currentTime - _lastDownloadProgress > 0.016 || final)) {
  71. _lastDownloadProgress = currentTime;
  72. callback(headers, @(headersContentLength), @(contentLength));
  73. }
  74. }
  75. - (BOOL)readAllPartsWithCompletionCallback:(RCTMultipartCallback)callback
  76. progressCallback:(RCTMultipartProgressCallback)progressCallback
  77. {
  78. NSInteger chunkStart = 0;
  79. NSInteger bytesSeen = 0;
  80. NSData *delimiter =
  81. [[NSString stringWithFormat:@"%@--%@%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
  82. NSData *closeDelimiter =
  83. [[NSString stringWithFormat:@"%@--%@--%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
  84. NSMutableData *content = [[NSMutableData alloc] initWithCapacity:1];
  85. NSDictionary *currentHeaders = nil;
  86. NSUInteger currentHeadersLength = 0;
  87. const NSUInteger bufferLen = 4 * 1024;
  88. uint8_t buffer[bufferLen];
  89. [_stream open];
  90. while (true) {
  91. BOOL isCloseDelimiter = NO;
  92. // Search only a subset of chunk that we haven't seen before + few bytes
  93. // to allow for the edge case when the delimiter is cut by read call
  94. NSInteger searchStart = MAX(bytesSeen - (NSInteger)closeDelimiter.length, chunkStart);
  95. NSRange remainingBufferRange = NSMakeRange(searchStart, content.length - searchStart);
  96. // Check for delimiters.
  97. NSRange range = [content rangeOfData:delimiter options:0 range:remainingBufferRange];
  98. if (range.location == NSNotFound) {
  99. isCloseDelimiter = YES;
  100. range = [content rangeOfData:closeDelimiter options:0 range:remainingBufferRange];
  101. }
  102. if (range.location == NSNotFound) {
  103. if (currentHeaders == nil) {
  104. // Check for the headers delimiter.
  105. NSData *headersMarker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding];
  106. NSRange headersRange = [content rangeOfData:headersMarker options:0 range:remainingBufferRange];
  107. if (headersRange.location != NSNotFound) {
  108. NSData *headersData = [content subdataWithRange:NSMakeRange(chunkStart, headersRange.location - chunkStart)];
  109. currentHeadersLength = headersData.length;
  110. currentHeaders = [self parseHeaders:headersData];
  111. }
  112. } else {
  113. // When headers are loaded start sending progress callbacks.
  114. [self emitProgress:currentHeaders
  115. contentLength:content.length - currentHeadersLength
  116. final:NO
  117. callback:progressCallback];
  118. }
  119. bytesSeen = content.length;
  120. NSInteger bytesRead = [_stream read:buffer maxLength:bufferLen];
  121. if (bytesRead <= 0 || _stream.streamError) {
  122. return NO;
  123. }
  124. [content appendBytes:buffer length:bytesRead];
  125. continue;
  126. }
  127. NSInteger chunkEnd = range.location;
  128. NSInteger length = chunkEnd - chunkStart;
  129. bytesSeen = chunkEnd;
  130. // Ignore preamble
  131. if (chunkStart > 0) {
  132. NSData *chunk = [content subdataWithRange:NSMakeRange(chunkStart, length)];
  133. [self emitProgress:currentHeaders
  134. contentLength:chunk.length - currentHeadersLength
  135. final:YES
  136. callback:progressCallback];
  137. [self emitChunk:chunk headers:currentHeaders callback:callback done:isCloseDelimiter];
  138. currentHeaders = nil;
  139. currentHeadersLength = 0;
  140. }
  141. if (isCloseDelimiter) {
  142. return YES;
  143. }
  144. chunkStart = chunkEnd + delimiter.length;
  145. }
  146. }
  147. @end