/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you 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 "CMISBroswerFormDataWriter.h" #import "CMISConstants.h" #import "CMISBrowserConstants.h" #import "CMISEnums.h" #import "CMISLog.h" #import "CMISMimeHelper.h" #import "CMISURLUtil.h" #import "CMISAcl.h" #import "CMISAce.h" #import "CMISProperties.h" NSString * const kCMISFormDataContentTypeUrlEncoded = @"application/x-www-form-urlencoded;charset=utf-8"; NSString * const kCMISFormDataContentTypeFormData = @"multipart/form-data; boundary="; @interface CMISBroswerFormDataWriter () @property (nonatomic, strong) NSInputStream *contentStream; @property (nonatomic, strong) NSMutableDictionary *parameters; @property (nonatomic, strong) NSString *boundary; @property (nonatomic, strong) NSString *mediaType; @property (nonatomic, strong) NSString *fileName; @end @implementation CMISBroswerFormDataWriter - (id)initWithAction:(NSString *)action { return [self initWithAction:action contentStream:nil mediaType:nil]; } - (id)initWithAction:(NSString *)action contentStream:(NSInputStream *)contentStream mediaType:(NSString *)mediaType { self = [super init]; if (self) { self.parameters = [[NSMutableDictionary alloc] init]; [self addParameter:kCMISBrowserJSONControlCmisAction value:action]; self.contentStream = contentStream; self.mediaType = mediaType; self.boundary = [NSString stringWithFormat:@"aPacHeCheMIStryoBjECtivEcmiS%x%a%x", (unsigned int) action.hash, CFAbsoluteTimeGetCurrent(), (unsigned int) self.hash]; } return self; } - (void)addParameter:(NSString *)name value:(id)value { if(!name || !value) { return; } [self.parameters setValue:[value description] forKey:name]; } - (void)addParameter:(NSString *)name boolValue:(BOOL)value { [self addParameter:name value:(value? kCMISParameterValueTrue : kCMISParameterValueFalse)]; } - (void)addAcesParameters:(CMISAcl *)acl { [self addAcesParameters:acl principalControl:kCMISBrowserJSONControlAddAcePrincipal permissionControl:kCMISBrowserJSONControlAddAcePermission]; } - (void)addRemoveAcesParameters:(CMISAcl *)acl { [self addAcesParameters:acl principalControl:kCMISBrowserJSONControlRemoveAcePrincipal permissionControl:kCMISBrowserJSONControlRemoveAcePermission]; } - (void)addAcesParameters:(CMISAcl *)acl principalControl:(NSString *)principalControl permissionControl:(NSString *)permissionControl { if (!acl || !acl.aces) { return; } int idx = 0; for (CMISAce *ace in acl.aces) { if (ace.principalId && ace.permissions.count > 0) { NSString *idxStr = [NSString stringWithFormat:@"[%d]", idx]; [self addParameter:[NSString stringWithFormat:@"%@%@", principalControl, idxStr] value:ace.principalId]; int permIdx = 0; for (NSString *perm in ace.permissions) { if (perm) { NSString *permIdxStr = [NSString stringWithFormat:@"[%d]", permIdx]; [self addParameter:[NSString stringWithFormat:@"%@%@%@", permissionControl, idxStr, permIdxStr] value:perm]; permIdx++; } } idx++; } } } - (void)addSuccinctFlag:(BOOL)succinct { if (succinct) { [self addParameter:kCMISBrowserJSONParameterSuccinct value:kCMISParameterValueTrue]; } } - (void)addPropertiesParameters:(CMISProperties *)properties { if (!properties) { return; } if(!self.fileName){ self.fileName = [properties propertyValueForId:kCMISPropertyName]; } int idx = 0; for (CMISPropertyData *prop in properties.propertyList) { NSString *idxStr = [NSString stringWithFormat:@"[%d]", idx]; [self addParameter:[NSString stringWithFormat:@"%@%@", kCMISBrowserJSONControlPropertyId, idxStr] value:prop.identifier]; if (prop.values && prop.values.count > 0) { if (prop.values.count == 1) { NSString *value = [self convertPropertyValue:prop.firstValue forPropertyType:prop.type]; [self addParameter:[NSString stringWithFormat:@"%@%@", kCMISBrowserJSONControlPropertyValue, idxStr] value:value]; } else { int vidx = 0; for (id obj in prop.values) { NSString *vidxStr = [NSString stringWithFormat:@"[%d]", vidx]; NSString *value = [self convertPropertyValue:obj forPropertyType:prop.type]; [self addParameter:[NSString stringWithFormat:@"%@%@%@", kCMISBrowserJSONControlPropertyValue, idxStr, vidxStr] value:value]; vidx++; } } } idx++; } } // TODO should this method be part of CMISPropertyData class (as class method?) - (NSString *)convertPropertyValue:(id)value forPropertyType:(CMISPropertyType)type { if (!value) { return nil; } if (type == CMISPropertyTypeBoolean) { return [value boolValue] ? kCMISParameterValueTrue : kCMISParameterValueFalse; } else if (type == CMISPropertyTypeDateTime) { if ([value isKindOfClass:NSDate.class]) { return [NSNumber numberWithUnsignedLongLong:[(NSDate *)value timeIntervalSince1970] * 1000].description; //seconds to milliseconds } else { CMISLogWarning(@"value is not a date!"); } } return value; } - (NSDictionary *)headers { NSString *contentType = self.contentStream == nil ? kCMISFormDataContentTypeUrlEncoded : [NSString stringWithFormat:@"%@%@", kCMISFormDataContentTypeFormData, self.boundary]; return @{@"Content-Type" : contentType}; } - (NSData *)body { if (self.contentStream == nil) { BOOL first = YES; NSData *amp = [@"&" dataUsingEncoding:NSUTF8StringEncoding]; NSMutableData *data = [[NSMutableData alloc] init]; for (NSString *parameterKey in self.parameters) { if (first) { first = NO; } else { [data appendData:amp]; } NSString *encodedParameterValue = [CMISURLUtil encodeUrlParameterValue:self.parameters[parameterKey]]; NSString *parameter = [NSString stringWithFormat:@"%@=%@", parameterKey, encodedParameterValue]; [data appendData:[parameter dataUsingEncoding:NSUTF8StringEncoding]]; } return data; } else { CMISLogError(@"this method should not be called when content stream is set. Use startData and endData method to retrieve the data."); return nil; } } - (NSData *)startData { if (self.contentStream) { NSMutableData *data = [[NSMutableData alloc] init]; [self appendLine:data]; // parameters for (NSString *paramKey in self.parameters) { [self appendLine:data string:[NSString stringWithFormat:@"--%@", self.boundary]]; [self appendLine:data string:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"", paramKey]]; [self appendLine:data string:@"Content-Type: text/plain; charset=utf-8"]; [self appendLine:data]; [self appendLine:data string:self.parameters[paramKey]]; } // content if (self.fileName == nil || self.fileName.length == 0) { self.fileName = @"content"; } if (self.mediaType == nil || [self.mediaType rangeOfString:@"/"].location < 1 || [self.mediaType rangeOfString:@"\n"].location > -1 || [self.mediaType rangeOfString:@"\r"].location > -1) { self.mediaType = kCMISMediaTypeOctetStream; } [self appendLine:data string:[NSString stringWithFormat:@"--%@", self.boundary]]; [self appendLine:data string:[NSString stringWithFormat:@"Content-Disposition: %@", [CMISMimeHelper encodeContentDisposition:kCMISMimeHelperDispositionFormDataContent fileName:self.fileName]]]; [self appendLine:data string:[NSString stringWithFormat:@"Content-Type: %@", self.mediaType]]; [self appendLine:data string:@"Content-Transfer-Encoding: binary"]; [self appendLine:data]; return data; } else { CMISLogError(@"this method should not be called when content stream is nil. Use body method to retrieve the data."); return nil; } } - (NSData *)endData { if (self.contentStream) { NSMutableData *data = [[NSMutableData alloc] init]; [self appendLine:data]; [self appendLine:data string:[NSString stringWithFormat:@"--%@--", self.boundary]]; return data; } else { CMISLogError(@"this method should not be called when content stream is nil. Use body method to retrieve the data."); return nil; } } - (void)appendLine:(NSMutableData *)data { [self appendLine:data string:nil]; } - (void)appendLine:(NSMutableData *)data string:(NSString *)s { s = s ? [NSString stringWithFormat:@"%@\r\n", s] : @"\r\n"; [data appendData:[s dataUsingEncoding:NSUTF8StringEncoding]]; } @end