209 lines
5.8 KiB
Objective-C
209 lines
5.8 KiB
Objective-C
// 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 "FIRCLSMultipartMimeStreamEncoder.h"
|
|
|
|
#import "FIRCLSByteUtility.h"
|
|
#import "FIRCLSLogger.h"
|
|
#import "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
|