| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- // Copyright 2019 Google
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #import "Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.h"
- #import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
- #import "Crashlytics/Shared/FIRCLSByteUtility.h"
- #import "Crashlytics/Shared/FIRCLSUUID.h"
- @interface FIRCLSMultipartMimeStreamEncoder () <NSStreamDelegate>
- @property(nonatomic) NSUInteger length;
- @property(nonatomic, copy) NSString *boundary;
- @property(nonatomic, copy, readonly) NSData *headerData;
- @property(nonatomic, copy, readonly) NSData *footerData;
- @property(nonatomic, strong) NSOutputStream *outputStream;
- @end
- @implementation FIRCLSMultipartMimeStreamEncoder
- + (void)populateRequest:(NSMutableURLRequest *)request
- withDataFromEncoder:(void (^)(FIRCLSMultipartMimeStreamEncoder *encoder))block {
- NSString *boundary = [self generateBoundary];
- NSOutputStream *stream = [NSOutputStream outputStreamToMemory];
- FIRCLSMultipartMimeStreamEncoder *encoder =
- [[FIRCLSMultipartMimeStreamEncoder alloc] initWithStream:stream andBoundary:boundary];
- [encoder encode:^{
- block(encoder);
- }];
- [request setValue:encoder.contentTypeHTTPHeaderValue forHTTPHeaderField:@"Content-Type"];
- [request setValue:encoder.contentLengthHTTPHeaderValue forHTTPHeaderField:@"Content-Length"];
- NSData *data = [stream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
- request.HTTPBody = data;
- }
- + (NSString *)contentTypeHTTPHeaderValueWithBoundary:(NSString *)boundary {
- return [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
- }
- + (instancetype)encoderWithStream:(NSOutputStream *)stream andBoundary:(NSString *)boundary {
- return [[self alloc] initWithStream:stream andBoundary:boundary];
- }
- + (NSString *)generateBoundary {
- return FIRCLSGenerateUUID();
- }
- - (instancetype)initWithStream:(NSOutputStream *)stream andBoundary:(NSString *)boundary {
- self = [super init];
- if (!self) {
- return nil;
- }
- self.outputStream = stream;
- if (!boundary) {
- boundary = [FIRCLSMultipartMimeStreamEncoder generateBoundary];
- }
- _boundary = boundary;
- return self;
- }
- - (void)encode:(void (^)(void))block {
- [self beginEncoding];
- block();
- [self endEncoding];
- }
- - (NSString *)contentTypeHTTPHeaderValue {
- return [[self class] contentTypeHTTPHeaderValueWithBoundary:self.boundary];
- }
- - (NSString *)contentLengthHTTPHeaderValue {
- return [NSString stringWithFormat:@"%lu", (unsigned long)_length];
- }
- #pragma - mark MIME part API
- - (void)beginEncoding {
- _length = 0;
- [self.outputStream open];
- [self writeData:self.headerData];
- }
- - (void)endEncoding {
- [self writeData:self.footerData];
- [self.outputStream close];
- }
- - (NSData *)headerData {
- return [@"MIME-Version: 1.0\r\n" dataUsingEncoding:NSUTF8StringEncoding];
- }
- - (NSData *)footerData {
- return [[NSString stringWithFormat:@"--%@--\r\n", self.boundary]
- dataUsingEncoding:NSUTF8StringEncoding];
- }
- - (void)addFileData:(NSData *)data
- fileName:(NSString *)fileName
- mimeType:(NSString *)mimeType
- fieldName:(NSString *)name {
- if ([data length] == 0) {
- FIRCLSErrorLog(@"Unable to MIME encode data with zero length (%@)", name);
- return;
- }
- if ([name length] == 0 || [fileName length] == 0) {
- FIRCLSErrorLog(@"name (%@) or fieldname (%@) is invalid", name, fileName);
- return;
- }
- NSMutableString *string;
- string = [NSMutableString
- stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",
- self.boundary, name, fileName];
- if (mimeType) {
- [string appendFormat:@"Content-Type: %@\r\n", mimeType];
- [string appendString:@"Content-Transfer-Encoding: binary\r\n\r\n"];
- } else {
- [string appendString:@"Content-Type: application/octet-stream\r\n\r\n"];
- }
- [self writeData:[string dataUsingEncoding:NSUTF8StringEncoding]];
- [self writeData:data];
- [self writeData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
- }
- - (void)addValue:(id)value fieldName:(NSString *)name {
- if ([name length] == 0 || !value || value == NSNull.null) {
- FIRCLSErrorLog(@"name (%@) or value (%@) is invalid", name, value);
- return;
- }
- NSMutableString *string;
- string =
- [NSMutableString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n",
- self.boundary, name];
- [string appendString:@"Content-Type: text/plain\r\n\r\n"];
- [string appendFormat:@"%@\r\n", value];
- [self writeData:[string dataUsingEncoding:NSUTF8StringEncoding]];
- }
- - (void)addFile:(NSURL *)fileURL
- fileName:(NSString *)fileName
- mimeType:(NSString *)mimeType
- fieldName:(NSString *)name {
- NSData *data = [NSData dataWithContentsOfURL:fileURL];
- [self addFileData:data fileName:fileName mimeType:mimeType fieldName:name];
- }
- - (BOOL)writeBytes:(const void *)bytes ofLength:(NSUInteger)length {
- if ([self.outputStream write:bytes maxLength:length] != length) {
- FIRCLSErrorLog(@"Failed to write bytes to stream");
- return NO;
- }
- _length += length;
- return YES;
- }
- - (void)writeData:(NSData *)data {
- FIRCLSEnumerateByteRangesOfNSDataUsingBlock(
- data, ^(const void *bytes, NSRange byteRange, BOOL *stop) {
- NSUInteger length = byteRange.length;
- if ([self.outputStream write:bytes maxLength:length] != length) {
- FIRCLSErrorLog(@"Failed to write data to stream");
- *stop = YES;
- return;
- }
- self->_length += length;
- });
- }
- @end
|