| |
| #import "MultipartMessageHeaderField.h" |
| #import "HTTPLogging.h" |
| |
| //----------------------------------------------------------------- |
| #pragma mark log level |
| |
| #ifdef DEBUG |
| static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; |
| #else |
| static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; |
| #endif |
| |
| |
| // helpers |
| int findChar(const char* str,NSUInteger length, char c); |
| NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding); |
| |
| //----------------------------------------------------------------- |
| // interface MultipartMessageHeaderField (private) |
| //----------------------------------------------------------------- |
| |
| |
| @interface MultipartMessageHeaderField (private) |
| -(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding; |
| @end |
| |
| |
| //----------------------------------------------------------------- |
| // implementation MultipartMessageHeaderField |
| //----------------------------------------------------------------- |
| |
| @implementation MultipartMessageHeaderField |
| @synthesize name,value,params; |
| |
| - (id) initWithData:(NSData *)data contentEncoding:(NSStringEncoding)encoding { |
| params = [[NSMutableDictionary alloc] initWithCapacity:1]; |
| |
| char* bytes = (char*)data.bytes; |
| NSUInteger length = data.length; |
| |
| int separatorOffset = findChar(bytes, length, ':'); |
| if( (-1 == separatorOffset) || (separatorOffset >= length-2) ) { |
| HTTPLogError(@"MultipartFormDataParser: Bad format.No colon in field header."); |
| // tear down |
| return nil; |
| } |
| |
| // header name is always ascii encoded; |
| name = [[NSString alloc] initWithBytes: bytes length: separatorOffset encoding: NSASCIIStringEncoding]; |
| if( nil == name ) { |
| HTTPLogError(@"MultipartFormDataParser: Bad MIME header name."); |
| // tear down |
| return nil; |
| } |
| |
| // skip the separator and the next ' ' symbol |
| bytes += separatorOffset + 2; |
| length -= separatorOffset + 2; |
| |
| separatorOffset = findChar(bytes, length, ';'); |
| if( separatorOffset == -1 ) { |
| // couldn't find ';', means we don't have extra params here. |
| value = [[NSString alloc] initWithBytes:bytes length: length encoding:encoding]; |
| |
| if( nil == value ) { |
| HTTPLogError(@"MultipartFormDataParser: Bad MIME header value for header name: '%@'",name); |
| // tear down |
| return nil; |
| } |
| return self; |
| } |
| |
| value = [[NSString alloc] initWithBytes:bytes length: separatorOffset encoding:encoding]; |
| HTTPLogVerbose(@"MultipartFormDataParser: Processing header field '%@' : '%@'",name,value); |
| // skipe the separator and the next ' ' symbol |
| bytes += separatorOffset + 2; |
| length -= separatorOffset + 2; |
| |
| // parse the "params" part of the header |
| if( ![self parseHeaderValueBytes:bytes length:length encoding:encoding] ) { |
| NSString* paramsStr = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding]; |
| HTTPLogError(@"MultipartFormDataParser: Bad params for header with name '%@' and value '%@'",name,value); |
| HTTPLogError(@"MultipartFormDataParser: Params str: %@",paramsStr); |
| |
| return nil; |
| } |
| return self; |
| } |
| |
| -(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding { |
| int offset = 0; |
| NSString* currentParam = nil; |
| BOOL insideQuote = NO; |
| while( offset < length ) { |
| if( bytes[offset] == '\"' ) { |
| if( !offset || bytes[offset-1] != '\\' ) { |
| insideQuote = !insideQuote; |
| } |
| } |
| |
| // skip quoted symbols |
| if( insideQuote ) { |
| ++ offset; |
| continue; |
| } |
| if( bytes[offset] == '=' ) { |
| if( currentParam ) { |
| // found '=' before terminating previous param. |
| return NO; |
| } |
| currentParam = [[NSString alloc] initWithBytes:bytes length:offset encoding:NSASCIIStringEncoding]; |
| |
| bytes+=offset + 1; |
| length -= offset + 1; |
| offset = 0; |
| continue; |
| } |
| if( bytes[offset] == ';' ) { |
| if( !currentParam ) { |
| // found ; before stating '='. |
| HTTPLogError(@"MultipartFormDataParser: Unexpected ';' when parsing header"); |
| return NO; |
| } |
| NSString* paramValue = extractParamValue(bytes, offset,encoding); |
| if( nil == paramValue ) { |
| HTTPLogWarn(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); |
| } |
| else { |
| #ifdef DEBUG |
| if( [params objectForKey:currentParam] ) { |
| HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in header %@",currentParam,name); |
| } |
| #endif |
| [params setObject:paramValue forKey:currentParam]; |
| HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); |
| } |
| |
| currentParam = nil; |
| |
| // ';' separator has ' ' following, skip them. |
| bytes+=offset + 2; |
| length -= offset + 2; |
| offset = 0; |
| } |
| ++ offset; |
| } |
| |
| // add last param |
| if( insideQuote ) { |
| HTTPLogWarn(@"MultipartFormDataParser: unterminated quote in header %@",name); |
| // return YES; |
| } |
| if( currentParam ) { |
| NSString* paramValue = extractParamValue(bytes, length, encoding); |
| |
| if( nil == paramValue ) { |
| HTTPLogError(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); |
| } |
| |
| #ifdef DEBUG |
| if( [params objectForKey:currentParam] ) { |
| HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in one header",currentParam); |
| } |
| #endif |
| [params setObject:paramValue forKey:currentParam]; |
| HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); |
| currentParam = nil; |
| } |
| |
| return YES; |
| } |
| |
| - (NSString *)description { |
| return [NSString stringWithFormat:@"%@:%@\n params: %@",name,value,params]; |
| } |
| |
| @end |
| |
| int findChar(const char* str, NSUInteger length, char c) { |
| int offset = 0; |
| while( offset < length ) { |
| if( str[offset] == c ) |
| return offset; |
| ++ offset; |
| } |
| return -1; |
| } |
| |
| NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding) { |
| if( !length ) |
| return nil; |
| NSMutableString* value = nil; |
| |
| if( bytes[0] == '"' ) { |
| // values may be quoted. Strip the quotes to get what we need. |
| value = [[NSMutableString alloc] initWithBytes:bytes + 1 length: length - 2 encoding:encoding]; |
| } |
| else { |
| value = [[NSMutableString alloc] initWithBytes:bytes length: length encoding:encoding]; |
| } |
| // restore escaped symbols |
| NSRange range= [value rangeOfString:@"\\"]; |
| while ( range.length ) { |
| [value deleteCharactersInRange:range]; |
| range.location ++; |
| range = [value rangeOfString:@"\\" options:NSLiteralSearch range: range]; |
| } |
| return value; |
| } |
| |