diff --git a/CGDWebServer/GCDWebServer.m b/CGDWebServer/GCDWebServer.m index cc8bb3e..a60535d 100644 --- a/CGDWebServer/GCDWebServer.m +++ b/CGDWebServer/GCDWebServer.m @@ -90,24 +90,42 @@ void GCDLogMessage(long level, NSString* format, ...) { #endif -NSString* GCDWebServerExtractHeaderParameter(NSString* header, NSString* attribute) { - NSString* value = nil; - if (header) { - NSScanner* scanner = [[NSScanner alloc] initWithString:header]; - NSString* string = [NSString stringWithFormat:@"%@=", attribute]; - if ([scanner scanUpToString:string intoString:NULL]) { - [scanner scanString:string intoString:NULL]; - if ([scanner scanString:@"\"" intoString:NULL]) { - [scanner scanUpToString:@"\"" intoString:&value]; - } else { - [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&value]; - } +NSString* GCDWebServerNormalizeHeaderValue(NSString* value) { + if (value) { + NSRange range = [value rangeOfString:@";"]; // Assume part before ";" separator is case-insensitive + if (range.location != NSNotFound) { + value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]]; + } else { + value = [value lowercaseString]; } - ARC_RELEASE(scanner); } return value; } +NSString* GCDWebServerTruncateHeaderValue(NSString* value) { + DCHECK([value isEqualToString:GCDWebServerNormalizeHeaderValue(value)]); + NSRange range = [value rangeOfString:@";"]; + return range.location != NSNotFound ? [value substringToIndex:range.location] : value; +} + +NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) { + DCHECK([value isEqualToString:GCDWebServerNormalizeHeaderValue(value)]); + NSString* parameter = nil; + NSScanner* scanner = [[NSScanner alloc] initWithString:value]; + [scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive + NSString* string = [NSString stringWithFormat:@"%@=", name]; + if ([scanner scanUpToString:string intoString:NULL]) { + [scanner scanString:string intoString:NULL]; + if ([scanner scanString:@"\"" intoString:NULL]) { + [scanner scanUpToString:@"\"" intoString:¶meter]; + } else { + [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter]; + } + } + ARC_RELEASE(scanner); + return parameter; +} + // http://www.w3schools.com/tags/ref_charactersets.asp NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) { NSStringEncoding encoding = kCFStringEncodingInvalidId; @@ -163,6 +181,7 @@ NSString* GCDWebServerUnescapeURLString(NSString* string) { return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8)); } +// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) { NSMutableDictionary* parameters = [NSMutableDictionary dictionary]; NSScanner* scanner = [[NSScanner alloc] initWithString:form]; diff --git a/CGDWebServer/GCDWebServerConnection.m b/CGDWebServer/GCDWebServerConnection.m index 5a263d1..9a08b7e 100644 --- a/CGDWebServer/GCDWebServerConnection.m +++ b/CGDWebServer/GCDWebServerConnection.m @@ -429,7 +429,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache")); } if (_response.contentType != nil) { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (ARC_BRIDGE CFStringRef)[_response.contentType lowercaseString]); + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (ARC_BRIDGE CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType)); } if (_response.contentLength != NSNotFound) { CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]); diff --git a/CGDWebServer/GCDWebServerDataRequest.m b/CGDWebServer/GCDWebServerDataRequest.m index ff29f20..0e9955b 100644 --- a/CGDWebServer/GCDWebServerDataRequest.m +++ b/CGDWebServer/GCDWebServerDataRequest.m @@ -77,7 +77,7 @@ - (NSString*)text { if (_text == nil) { if ([self.contentType hasPrefix:@"text/"]) { - NSString* charset = GCDWebServerExtractHeaderParameter(self.contentType, @"charset"); + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); _text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; } else { DNOT_REACHED(); diff --git a/CGDWebServer/GCDWebServerMultiPartFormRequest.h b/CGDWebServer/GCDWebServerMultiPartFormRequest.h index e318d67..b09a169 100644 --- a/CGDWebServer/GCDWebServerMultiPartFormRequest.h +++ b/CGDWebServer/GCDWebServerMultiPartFormRequest.h @@ -28,8 +28,8 @@ #import "GCDWebServerRequest.h" @interface GCDWebServerMultiPart : NSObject -@property(nonatomic, readonly) NSString* contentType; // May be nil -@property(nonatomic, readonly) NSString* mimeType; // Defaults to "text/plain" per specifications if undefined +@property(nonatomic, readonly) NSString* contentType; // Defaults to "text/plain" per specifications if undefined +@property(nonatomic, readonly) NSString* mimeType; @end @interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart diff --git a/CGDWebServer/GCDWebServerMultiPartFormRequest.m b/CGDWebServer/GCDWebServerMultiPartFormRequest.m index 69f031a..2659be6 100644 --- a/CGDWebServer/GCDWebServerMultiPartFormRequest.m +++ b/CGDWebServer/GCDWebServerMultiPartFormRequest.m @@ -55,13 +55,7 @@ static NSData* _dashNewlineData = nil; - (id)initWithContentType:(NSString*)contentType { if ((self = [super init])) { _contentType = [contentType copy]; - NSArray* components = [_contentType componentsSeparatedByString:@";"]; - if (components.count) { - _mimeType = ARC_RETAIN([[components objectAtIndex:0] lowercaseString]); - } - if (_mimeType == nil) { - _mimeType = @"text/plain"; - } + _mimeType = ARC_RETAIN(GCDWebServerTruncateHeaderValue(_contentType)); } return self; } @@ -90,8 +84,8 @@ static NSData* _dashNewlineData = nil; if ((self = [super initWithContentType:contentType])) { _data = ARC_RETAIN(data); - if ([self.mimeType hasPrefix:@"text/"]) { - NSString* charset = GCDWebServerExtractHeaderParameter(self.contentType, @"charset"); + if ([self.contentType hasPrefix:@"text/"]) { + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); _string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)]; } } @@ -187,7 +181,7 @@ static NSData* _dashNewlineData = nil; - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { - NSString* boundary = GCDWebServerExtractHeaderParameter(self.contentType, @"boundary"); + NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary"); if (boundary) { NSData* data = [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding]; _boundary = ARC_RETAIN(data); @@ -210,7 +204,7 @@ static NSData* _dashNewlineData = nil; return YES; } -// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4 +// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 - (BOOL)_parseData { BOOL success = YES; @@ -234,14 +228,17 @@ static NSData* _dashNewlineData = nil; NSString* controlName = nil; NSString* fileName = nil; NSDictionary* headers = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(message)); - NSString* contentDisposition = [headers objectForKey:@"Content-Disposition"]; - if ([[contentDisposition lowercaseString] hasPrefix:@"form-data;"]) { - controlName = GCDWebServerExtractHeaderParameter(contentDisposition, @"name"); - fileName = GCDWebServerExtractHeaderParameter(contentDisposition, @"filename"); + NSString* contentDisposition = GCDWebServerNormalizeHeaderValue([headers objectForKey:@"Content-Disposition"]); + if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) { + controlName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name"); + fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); } _controlName = [controlName copy]; _fileName = [fileName copy]; - _contentType = ARC_RETAIN([headers objectForKey:@"Content-Type"]); + _contentType = ARC_RETAIN(GCDWebServerNormalizeHeaderValue([headers objectForKey:@"Content-Type"])); + if (_contentType == nil) { + _contentType = @"text/plain"; + } } CFRelease(message); if (_controlName) { diff --git a/CGDWebServer/GCDWebServerPrivate.h b/CGDWebServer/GCDWebServerPrivate.h index 6d94c6f..d25c819 100644 --- a/CGDWebServer/GCDWebServerPrivate.h +++ b/CGDWebServer/GCDWebServerPrivate.h @@ -108,7 +108,9 @@ static inline BOOL GCDWebServerIsValidByteRange(NSRange range) { return ((range.location != NSNotFound) || (range.length > 0)); } -extern NSString* GCDWebServerExtractHeaderParameter(NSString* header, NSString* attribute); +extern NSString* GCDWebServerNormalizeHeaderValue(NSString* value); +extern NSString* GCDWebServerTruncateHeaderValue(NSString* value); +extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute); extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset); extern NSString* GCDWebServerFormatHTTPDate(NSDate* date); extern NSDate* GCDWebServerParseHTTPDate(NSString* string); diff --git a/CGDWebServer/GCDWebServerRequest.m b/CGDWebServer/GCDWebServerRequest.m index 827c0cd..72bd41d 100644 --- a/CGDWebServer/GCDWebServerRequest.m +++ b/CGDWebServer/GCDWebServerRequest.m @@ -169,8 +169,8 @@ _path = [path copy]; _query = ARC_RETAIN(query); - _type = ARC_RETAIN([[_headers objectForKey:@"Content-Type"] lowercaseString]); - _chunked = [[[_headers objectForKey:@"Transfer-Encoding"] lowercaseString] isEqualToString:@"chunked"]; + _type = ARC_RETAIN(GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"])); + _chunked = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"]; NSString* lengthHeader = [_headers objectForKey:@"Content-Length"]; if (lengthHeader) { NSInteger length = [lengthHeader integerValue]; @@ -204,7 +204,7 @@ _noneMatch = ARC_RETAIN([_headers objectForKey:@"If-None-Match"]); _range = NSMakeRange(NSNotFound, 0); - NSString* rangeHeader = [[_headers objectForKey:@"Range"] lowercaseString]; + NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]); if (rangeHeader) { if ([rangeHeader hasPrefix:@"bytes="]) { NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","]; @@ -278,7 +278,7 @@ - (void)prepareForWriting { _writer = self; - if ([[[self.headers objectForKey:@"Content-Encoding"] lowercaseString] isEqualToString:@"gzip"]) { + if ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) { GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer]; [_decoders addObject:decoder]; ARC_RELEASE(decoder); diff --git a/CGDWebServer/GCDWebServerURLEncodedFormRequest.m b/CGDWebServer/GCDWebServerURLEncodedFormRequest.m index 7962e54..31207c4 100644 --- a/CGDWebServer/GCDWebServerURLEncodedFormRequest.m +++ b/CGDWebServer/GCDWebServerURLEncodedFormRequest.m @@ -52,7 +52,7 @@ return NO; } - NSString* charset = GCDWebServerExtractHeaderParameter(self.contentType, @"charset"); + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; _arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string)); DCHECK(_arguments);