FIRCLSMultipartMimeStreamEncoder.m 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Copyright 2019 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.h"
  15. #import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
  16. #import "Crashlytics/Shared/FIRCLSByteUtility.h"
  17. #import "Crashlytics/Shared/FIRCLSUUID.h"
  18. @interface FIRCLSMultipartMimeStreamEncoder () <NSStreamDelegate>
  19. @property(nonatomic) NSUInteger length;
  20. @property(nonatomic, copy) NSString *boundary;
  21. @property(nonatomic, copy, readonly) NSData *headerData;
  22. @property(nonatomic, copy, readonly) NSData *footerData;
  23. @property(nonatomic, strong) NSOutputStream *outputStream;
  24. @end
  25. @implementation FIRCLSMultipartMimeStreamEncoder
  26. + (void)populateRequest:(NSMutableURLRequest *)request
  27. withDataFromEncoder:(void (^)(FIRCLSMultipartMimeStreamEncoder *encoder))block {
  28. NSString *boundary = [self generateBoundary];
  29. NSOutputStream *stream = [NSOutputStream outputStreamToMemory];
  30. FIRCLSMultipartMimeStreamEncoder *encoder =
  31. [[FIRCLSMultipartMimeStreamEncoder alloc] initWithStream:stream andBoundary:boundary];
  32. [encoder encode:^{
  33. block(encoder);
  34. }];
  35. [request setValue:encoder.contentTypeHTTPHeaderValue forHTTPHeaderField:@"Content-Type"];
  36. [request setValue:encoder.contentLengthHTTPHeaderValue forHTTPHeaderField:@"Content-Length"];
  37. NSData *data = [stream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
  38. request.HTTPBody = data;
  39. }
  40. + (NSString *)contentTypeHTTPHeaderValueWithBoundary:(NSString *)boundary {
  41. return [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
  42. }
  43. + (instancetype)encoderWithStream:(NSOutputStream *)stream andBoundary:(NSString *)boundary {
  44. return [[self alloc] initWithStream:stream andBoundary:boundary];
  45. }
  46. + (NSString *)generateBoundary {
  47. return FIRCLSGenerateUUID();
  48. }
  49. - (instancetype)initWithStream:(NSOutputStream *)stream andBoundary:(NSString *)boundary {
  50. self = [super init];
  51. if (!self) {
  52. return nil;
  53. }
  54. self.outputStream = stream;
  55. if (!boundary) {
  56. boundary = [FIRCLSMultipartMimeStreamEncoder generateBoundary];
  57. }
  58. _boundary = boundary;
  59. return self;
  60. }
  61. - (void)encode:(void (^)(void))block {
  62. [self beginEncoding];
  63. block();
  64. [self endEncoding];
  65. }
  66. - (NSString *)contentTypeHTTPHeaderValue {
  67. return [[self class] contentTypeHTTPHeaderValueWithBoundary:self.boundary];
  68. }
  69. - (NSString *)contentLengthHTTPHeaderValue {
  70. return [NSString stringWithFormat:@"%lu", (unsigned long)_length];
  71. }
  72. #pragma - mark MIME part API
  73. - (void)beginEncoding {
  74. _length = 0;
  75. [self.outputStream open];
  76. [self writeData:self.headerData];
  77. }
  78. - (void)endEncoding {
  79. [self writeData:self.footerData];
  80. [self.outputStream close];
  81. }
  82. - (NSData *)headerData {
  83. return [@"MIME-Version: 1.0\r\n" dataUsingEncoding:NSUTF8StringEncoding];
  84. }
  85. - (NSData *)footerData {
  86. return [[NSString stringWithFormat:@"--%@--\r\n", self.boundary]
  87. dataUsingEncoding:NSUTF8StringEncoding];
  88. }
  89. - (void)addFileData:(NSData *)data
  90. fileName:(NSString *)fileName
  91. mimeType:(NSString *)mimeType
  92. fieldName:(NSString *)name {
  93. if ([data length] == 0) {
  94. FIRCLSErrorLog(@"Unable to MIME encode data with zero length (%@)", name);
  95. return;
  96. }
  97. if ([name length] == 0 || [fileName length] == 0) {
  98. FIRCLSErrorLog(@"name (%@) or fieldname (%@) is invalid", name, fileName);
  99. return;
  100. }
  101. NSMutableString *string;
  102. string = [NSMutableString
  103. stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",
  104. self.boundary, name, fileName];
  105. if (mimeType) {
  106. [string appendFormat:@"Content-Type: %@\r\n", mimeType];
  107. [string appendString:@"Content-Transfer-Encoding: binary\r\n\r\n"];
  108. } else {
  109. [string appendString:@"Content-Type: application/octet-stream\r\n\r\n"];
  110. }
  111. [self writeData:[string dataUsingEncoding:NSUTF8StringEncoding]];
  112. [self writeData:data];
  113. [self writeData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
  114. }
  115. - (void)addValue:(id)value fieldName:(NSString *)name {
  116. if ([name length] == 0 || !value || value == NSNull.null) {
  117. FIRCLSErrorLog(@"name (%@) or value (%@) is invalid", name, value);
  118. return;
  119. }
  120. NSMutableString *string;
  121. string =
  122. [NSMutableString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n",
  123. self.boundary, name];
  124. [string appendString:@"Content-Type: text/plain\r\n\r\n"];
  125. [string appendFormat:@"%@\r\n", value];
  126. [self writeData:[string dataUsingEncoding:NSUTF8StringEncoding]];
  127. }
  128. - (void)addFile:(NSURL *)fileURL
  129. fileName:(NSString *)fileName
  130. mimeType:(NSString *)mimeType
  131. fieldName:(NSString *)name {
  132. NSData *data = [NSData dataWithContentsOfURL:fileURL];
  133. [self addFileData:data fileName:fileName mimeType:mimeType fieldName:name];
  134. }
  135. - (BOOL)writeBytes:(const void *)bytes ofLength:(NSUInteger)length {
  136. if ([self.outputStream write:bytes maxLength:length] != length) {
  137. FIRCLSErrorLog(@"Failed to write bytes to stream");
  138. return NO;
  139. }
  140. _length += length;
  141. return YES;
  142. }
  143. - (void)writeData:(NSData *)data {
  144. FIRCLSEnumerateByteRangesOfNSDataUsingBlock(
  145. data, ^(const void *bytes, NSRange byteRange, BOOL *stop) {
  146. NSUInteger length = byteRange.length;
  147. if ([self.outputStream write:bytes maxLength:length] != length) {
  148. FIRCLSErrorLog(@"Failed to write data to stream");
  149. *stop = YES;
  150. return;
  151. }
  152. self->_length += length;
  153. });
  154. }
  155. @end