Browse Source

Properly handle casing of header values

Pierre-Olivier Latour 11 years ago
parent
commit
811e45ab26

+ 32 - 13
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:&parameter];
+    } else {
+      [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
+    }
+  }
+  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];

+ 1 - 1
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]);

+ 1 - 1
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();

+ 2 - 2
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

+ 13 - 16
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) {

+ 3 - 1
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);

+ 4 - 4
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);

+ 1 - 1
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);