Эх сурвалжийг харах

Moved functions to GCDWebServerFunctions.[h/m]

Pierre-Olivier Latour 11 жил өмнө
parent
commit
0852bf2d05

+ 0 - 18
CGDWebServer/GCDWebServer.h

@@ -42,24 +42,6 @@ typedef NS_ENUM(int, GCDWebServerLogLevel) {
 typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
 typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
-NSString* GCDWebServerEscapeURLString(NSString* string);
-NSString* GCDWebServerUnescapeURLString(NSString* string);
-NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
-NSString* GCDWebServerGetPrimaryIPv4Address();  // Returns IPv4 address of primary connected service on OS X or of WiFi interface on iOS if connected
-NSString* GCDWebServerFormatRFC822(NSDate* date);
-NSDate* GCDWebServerParseRFC822(NSString* string);
-NSString* GCDWebServerFormatISO8601(NSDate* date);
-NSDate* GCDWebServerParseISO8601(NSString* string);
-
-#ifdef __cplusplus
-}
-#endif
-
 @interface GCDWebServer : NSObject
 @property(nonatomic, readonly, getter=isRunning) BOOL running;
 @property(nonatomic, readonly) NSUInteger port;

+ 2 - 236
CGDWebServer/GCDWebServer.m

@@ -26,16 +26,7 @@
  */
 
 #import <TargetConditionals.h>
-#if TARGET_OS_IPHONE
-#import <MobileCoreServices/MobileCoreServices.h>
-#else
-#import <SystemConfiguration/SystemConfiguration.h>
-#endif
-
 #import <netinet/in.h>
-#import <ifaddrs.h>
-#import <net/if.h>
-#import <netdb.h>
 
 #import "GCDWebServerPrivate.h"
 
@@ -74,9 +65,6 @@ GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Debug;
 #endif
 #endif
 
-static NSDateFormatter* _dateFormatterRFC822 = nil;
-static NSDateFormatter* _dateFormatterISO8601 = nil;
-static dispatch_queue_t _dateFormatterQueue = NULL;
 #if !TARGET_OS_IPHONE
 static BOOL _run;
 #endif
@@ -95,207 +83,6 @@ void GCDLogMessage(GCDWebServerLogLevel level, NSString* format, ...) {
 
 #endif
 
-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];
-    }
-  }
-  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;
-  if (charset) {
-    encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
-  }
-  return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
-}
-
-NSString* GCDWebServerFormatRFC822(NSDate* date) {
-  __block NSString* string;
-  dispatch_sync(_dateFormatterQueue, ^{
-    string = [_dateFormatterRFC822 stringFromDate:date];
-  });
-  return string;
-}
-
-NSDate* GCDWebServerParseRFC822(NSString* string) {
-  __block NSDate* date;
-  dispatch_sync(_dateFormatterQueue, ^{
-    date = [_dateFormatterRFC822 dateFromString:string];
-  });
-  return date;
-}
-
-NSString* GCDWebServerFormatISO8601(NSDate* date) {
-  __block NSString* string;
-  dispatch_sync(_dateFormatterQueue, ^{
-    string = [_dateFormatterISO8601 stringFromDate:date];
-  });
-  return string;
-}
-
-NSDate* GCDWebServerParseISO8601(NSString* string) {
-  __block NSDate* date;
-  dispatch_sync(_dateFormatterQueue, ^{
-    date = [_dateFormatterISO8601 dateFromString:string];
-  });
-  return date;
-}
-
-static inline BOOL _IsTextContentType(NSString* type) {
-  return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]);
-}
-
-NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
-  if (_IsTextContentType(type)) {
-    NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
-    NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
-    if (string) {
-      return ARC_AUTORELEASE(string);
-    }
-  }
-  return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
-}
-
-NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
-  static NSDictionary* _overrides = nil;
-  if (_overrides == nil) {
-    _overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
-                  @"text/css", @"css",
-                  nil];
-  }
-  NSString* mimeType = nil;
-  extension = [extension lowercaseString];
-  if (extension.length) {
-    mimeType = [_overrides objectForKey:extension];
-    if (mimeType == nil) {
-      CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (ARC_BRIDGE CFStringRef)extension, NULL);
-      if (uti) {
-        mimeType = ARC_BRIDGE_RELEASE(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
-        CFRelease(uti);
-      }
-    }
-  }
-  return mimeType ? mimeType : kGCDWebServerDefaultMimeType;
-}
-
-NSString* GCDWebServerEscapeURLString(NSString* string) {
-  return ARC_BRIDGE_RELEASE(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
-}
-
-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];
-  [scanner setCharactersToBeSkipped:nil];
-  while (1) {
-    NSString* key = nil;
-    if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) {
-      break;
-    }
-    [scanner setScanLocation:([scanner scanLocation] + 1)];
-    
-    NSString* value = nil;
-    if (![scanner scanUpToString:@"&" intoString:&value]) {
-      break;
-    }
-    
-    key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
-    value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
-    if (key && value) {
-      [parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
-    } else {
-      DNOT_REACHED();
-    }
-    
-    if ([scanner isAtEnd]) {
-      break;
-    }
-    [scanner setScanLocation:([scanner scanLocation] + 1)];
-  }
-  ARC_RELEASE(scanner);
-  return parameters;
-}
-
-NSString* GCDWebServerGetPrimaryIPv4Address() {
-  NSString* address = nil;
-#if TARGET_OS_IPHONE
-#if !TARGET_IPHONE_SIMULATOR
-  const char* primaryInterface = "en0";  // WiFi interface on iOS
-#endif
-#else
-  const char* primaryInterface = NULL;
-  SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
-  if (store) {
-    CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4"));
-    if (info) {
-      primaryInterface = [[NSString stringWithString:[(ARC_BRIDGE NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
-      CFRelease(info);
-    }
-    CFRelease(store);
-  }
-  if (primaryInterface == NULL) {
-    primaryInterface = "lo0";
-  }
-#endif
-  struct ifaddrs* list;
-  if (getifaddrs(&list) >= 0) {
-    for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
-#if TARGET_IPHONE_SIMULATOR
-      if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))  // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
-#else
-      if (strcmp(ifap->ifa_name, primaryInterface))
-#endif
-      {
-        continue;
-      }
-      if ((ifap->ifa_flags & IFF_UP) && (ifap->ifa_addr->sa_family == AF_INET)) {
-        char buffer[NI_MAXHOST];
-        if (getnameinfo(ifap->ifa_addr, ifap->ifa_addr->sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST | NI_NOFQDN) >= 0) {
-          address = [NSString stringWithUTF8String:buffer];
-        }
-        break;
-      }
-    }
-    freeifaddrs(list);
-  }
-  return address;
-}
-
 #if !TARGET_OS_IPHONE
 
 static void _SignalHandler(int signal) {
@@ -341,29 +128,8 @@ static void _SignalHandler(int signal) {
 
 #endif
 
-// HTTP/1.1 server must use RFC822
-// TODO: Handle RFC 850 and ANSI C's asctime() format (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3)
 + (void)initialize {
-  if (_dateFormatterRFC822 == nil) {
-    DCHECK([NSThread isMainThread]);  // NSDateFormatter should be initialized on main thread
-    _dateFormatterRFC822 = [[NSDateFormatter alloc] init];
-    _dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
-    _dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
-    _dateFormatterRFC822.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
-    DCHECK(_dateFormatterRFC822);
-  }
-  if (_dateFormatterISO8601 == nil) {
-    DCHECK([NSThread isMainThread]);  // NSDateFormatter should be initialized on main thread
-    _dateFormatterISO8601 = [[NSDateFormatter alloc] init];
-    _dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
-    _dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
-    _dateFormatterISO8601.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
-    DCHECK(_dateFormatterISO8601);
-  }
-  if (_dateFormatterQueue == NULL) {
-    _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
-    DCHECK(_dateFormatterQueue);
-  }
+  GCDWebServerInitializeFunctions();
 }
 
 - (instancetype)init {
@@ -750,7 +516,7 @@ static void _LogResult(NSString* format, ...) {
                         _LogResult(@"  Bodies not matching:\n    Expected: %lu bytes\n      Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
                         success = NO;
 #ifndef NDEBUG
-                        if (_IsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
+                        if (GCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
                           NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
                           NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
                           if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {

+ 46 - 0
CGDWebServer/GCDWebServerFunctions.h

@@ -0,0 +1,46 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
+NSString* GCDWebServerEscapeURLString(NSString* string);
+NSString* GCDWebServerUnescapeURLString(NSString* string);
+NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
+NSString* GCDWebServerGetPrimaryIPv4Address();  // Returns IPv4 address of primary connected service on OS X or of WiFi interface on iOS if connected
+NSString* GCDWebServerFormatRFC822(NSDate* date);
+NSDate* GCDWebServerParseRFC822(NSString* string);
+NSString* GCDWebServerFormatISO8601(NSDate* date);
+NSDate* GCDWebServerParseISO8601(NSString* string);
+
+#ifdef __cplusplus
+}
+#endif

+ 268 - 0
CGDWebServer/GCDWebServerFunctions.m

@@ -0,0 +1,268 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <TargetConditionals.h>
+#if TARGET_OS_IPHONE
+#import <MobileCoreServices/MobileCoreServices.h>
+#else
+#import <SystemConfiguration/SystemConfiguration.h>
+#endif
+
+#import <ifaddrs.h>
+#import <net/if.h>
+#import <netdb.h>
+
+#import "GCDWebServerPrivate.h"
+
+static NSDateFormatter* _dateFormatterRFC822 = nil;
+static NSDateFormatter* _dateFormatterISO8601 = nil;
+static dispatch_queue_t _dateFormatterQueue = NULL;
+
+// HTTP/1.1 server must use RFC822
+// TODO: Handle RFC 850 and ANSI C's asctime() format (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3)
+void GCDWebServerInitializeFunctions() {
+  DCHECK([NSThread isMainThread]);  // NSDateFormatter should be initialized on main thread
+  if (_dateFormatterRFC822 == nil) {
+    _dateFormatterRFC822 = [[NSDateFormatter alloc] init];
+    _dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
+    _dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
+    _dateFormatterRFC822.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
+    DCHECK(_dateFormatterRFC822);
+  }
+  if (_dateFormatterISO8601 == nil) {
+    _dateFormatterISO8601 = [[NSDateFormatter alloc] init];
+    _dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
+    _dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
+    _dateFormatterISO8601.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
+    DCHECK(_dateFormatterISO8601);
+  }
+  if (_dateFormatterQueue == NULL) {
+    _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+    DCHECK(_dateFormatterQueue);
+  }
+}
+
+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];
+    }
+  }
+  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;
+  if (charset) {
+    encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
+  }
+  return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
+}
+
+NSString* GCDWebServerFormatRFC822(NSDate* date) {
+  __block NSString* string;
+  dispatch_sync(_dateFormatterQueue, ^{
+    string = [_dateFormatterRFC822 stringFromDate:date];
+  });
+  return string;
+}
+
+NSDate* GCDWebServerParseRFC822(NSString* string) {
+  __block NSDate* date;
+  dispatch_sync(_dateFormatterQueue, ^{
+    date = [_dateFormatterRFC822 dateFromString:string];
+  });
+  return date;
+}
+
+NSString* GCDWebServerFormatISO8601(NSDate* date) {
+  __block NSString* string;
+  dispatch_sync(_dateFormatterQueue, ^{
+    string = [_dateFormatterISO8601 stringFromDate:date];
+  });
+  return string;
+}
+
+NSDate* GCDWebServerParseISO8601(NSString* string) {
+  __block NSDate* date;
+  dispatch_sync(_dateFormatterQueue, ^{
+    date = [_dateFormatterISO8601 dateFromString:string];
+  });
+  return date;
+}
+
+BOOL GCDWebServerIsTextContentType(NSString* type) {
+  return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]);
+}
+
+NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
+  if (GCDWebServerIsTextContentType(type)) {
+    NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
+    NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
+    if (string) {
+      return ARC_AUTORELEASE(string);
+    }
+  }
+  return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
+}
+
+NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
+  static NSDictionary* _overrides = nil;
+  if (_overrides == nil) {
+    _overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
+                  @"text/css", @"css",
+                  nil];
+  }
+  NSString* mimeType = nil;
+  extension = [extension lowercaseString];
+  if (extension.length) {
+    mimeType = [_overrides objectForKey:extension];
+    if (mimeType == nil) {
+      CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (ARC_BRIDGE CFStringRef)extension, NULL);
+      if (uti) {
+        mimeType = ARC_BRIDGE_RELEASE(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
+        CFRelease(uti);
+      }
+    }
+  }
+  return mimeType ? mimeType : kGCDWebServerDefaultMimeType;
+}
+
+NSString* GCDWebServerEscapeURLString(NSString* string) {
+  return ARC_BRIDGE_RELEASE(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
+}
+
+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];
+  [scanner setCharactersToBeSkipped:nil];
+  while (1) {
+    NSString* key = nil;
+    if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) {
+      break;
+    }
+    [scanner setScanLocation:([scanner scanLocation] + 1)];
+    
+    NSString* value = nil;
+    if (![scanner scanUpToString:@"&" intoString:&value]) {
+      break;
+    }
+    
+    key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
+    value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
+    if (key && value) {
+      [parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
+    } else {
+      DNOT_REACHED();
+    }
+    
+    if ([scanner isAtEnd]) {
+      break;
+    }
+    [scanner setScanLocation:([scanner scanLocation] + 1)];
+  }
+  ARC_RELEASE(scanner);
+  return parameters;
+}
+
+NSString* GCDWebServerGetPrimaryIPv4Address() {
+  NSString* address = nil;
+#if TARGET_OS_IPHONE
+#if !TARGET_IPHONE_SIMULATOR
+  const char* primaryInterface = "en0";  // WiFi interface on iOS
+#endif
+#else
+  const char* primaryInterface = NULL;
+  SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
+  if (store) {
+    CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4"));
+    if (info) {
+      primaryInterface = [[NSString stringWithString:[(ARC_BRIDGE NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
+      CFRelease(info);
+    }
+    CFRelease(store);
+  }
+  if (primaryInterface == NULL) {
+    primaryInterface = "lo0";
+  }
+#endif
+  struct ifaddrs* list;
+  if (getifaddrs(&list) >= 0) {
+    for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
+#if TARGET_IPHONE_SIMULATOR
+      if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))  // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
+#else
+      if (strcmp(ifap->ifa_name, primaryInterface))
+#endif
+      {
+        continue;
+      }
+      if ((ifap->ifa_flags & IFF_UP) && (ifap->ifa_addr->sa_family == AF_INET)) {
+        char buffer[NI_MAXHOST];
+        if (getnameinfo(ifap->ifa_addr, ifap->ifa_addr->sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST | NI_NOFQDN) >= 0) {
+          address = [NSString stringWithUTF8String:buffer];
+        }
+        break;
+      }
+    }
+    freeifaddrs(list);
+  }
+  return address;
+}

+ 4 - 0
CGDWebServer/GCDWebServerPrivate.h

@@ -54,7 +54,9 @@
 #endif
 
 #import "GCDWebServerHTTPStatusCodes.h"
+#import "GCDWebServerFunctions.h"
 
+#import "GCDWebServer.h"
 #import "GCDWebServerConnection.h"
 
 #import "GCDWebServerDataRequest.h"
@@ -112,10 +114,12 @@ static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
   return ((range.location != NSNotFound) || (range.length > 0));
 }
 
+extern void GCDWebServerInitializeFunctions();
 extern NSString* GCDWebServerNormalizeHeaderValue(NSString* value);
 extern NSString* GCDWebServerTruncateHeaderValue(NSString* value);
 extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute);
 extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
+extern BOOL GCDWebServerIsTextContentType(NSString* type);
 extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
 
 @interface GCDWebServerConnection ()

+ 2 - 0
GCDWebDAVServer/GCDWebDAVServer.m

@@ -32,6 +32,8 @@
 
 #import "GCDWebDAVServer.h"
 
+#import "GCDWebServerFunctions.h"
+
 #import "GCDWebServerDataRequest.h"
 #import "GCDWebServerFileRequest.h"
 

+ 8 - 0
GCDWebServer.xcodeproj/project.pbxproj

@@ -40,6 +40,8 @@
 		E221129D1690B7BA0048D2B2 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */; };
 		E276647C18F3BC2100A034BA /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E276647B18F3BC2100A034BA /* GCDWebServerErrorResponse.m */; };
 		E276647D18F3BC2100A034BA /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E276647B18F3BC2100A034BA /* GCDWebServerErrorResponse.m */; };
+		E28BAE1218F99A600095C089 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE1118F99A600095C089 /* GCDWebServerFunctions.m */; };
+		E28BAE1318F99A600095C089 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE1118F99A600095C089 /* GCDWebServerFunctions.m */; };
 		E2A0E7ED18F1D03700C580B1 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */; };
 		E2A0E7EE18F1D03700C580B1 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */; };
 		E2A0E7F118F1D12E00C580B1 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7F018F1D12E00C580B1 /* GCDWebServerFileResponse.m */; };
@@ -121,6 +123,8 @@
 		E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
 		E276647A18F3BC2100A034BA /* GCDWebServerErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerErrorResponse.h; sourceTree = "<group>"; };
 		E276647B18F3BC2100A034BA /* GCDWebServerErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerErrorResponse.m; sourceTree = "<group>"; };
+		E28BAE1018F99A600095C089 /* GCDWebServerFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFunctions.h; sourceTree = "<group>"; };
+		E28BAE1118F99A600095C089 /* GCDWebServerFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFunctions.m; sourceTree = "<group>"; };
 		E2A0E7EB18F1D03700C580B1 /* GCDWebServerDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataResponse.h; sourceTree = "<group>"; };
 		E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataResponse.m; sourceTree = "<group>"; };
 		E2A0E7EF18F1D12E00C580B1 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = "<group>"; };
@@ -217,6 +221,8 @@
 				E2A0E7FC18F1D36C00C580B1 /* GCDWebServerFileRequest.m */,
 				E2A0E7EF18F1D12E00C580B1 /* GCDWebServerFileResponse.h */,
 				E2A0E7F018F1D12E00C580B1 /* GCDWebServerFileResponse.m */,
+				E28BAE1018F99A600095C089 /* GCDWebServerFunctions.h */,
+				E28BAE1118F99A600095C089 /* GCDWebServerFunctions.m */,
 				E2A0E81018F3737B00C580B1 /* GCDWebServerHTTPStatusCodes.h */,
 				E2A0E7FF18F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.h */,
 				E2A0E80018F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m */,
@@ -393,6 +399,7 @@
 				E2A0E80A18F3432600C580B1 /* GCDWebDAVServer.m in Sources */,
 				E2BE850C18E785940061360B /* GCDWebUploader.m in Sources */,
 				E221128F1690B6470048D2B2 /* main.m in Sources */,
+				E28BAE1218F99A600095C089 /* GCDWebServerFunctions.m in Sources */,
 				E2A0E7F118F1D12E00C580B1 /* GCDWebServerFileResponse.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -416,6 +423,7 @@
 				E2A0E7FE18F1D36C00C580B1 /* GCDWebServerFileRequest.m in Sources */,
 				E2A0E7F618F1D1E500C580B1 /* GCDWebServerStreamingResponse.m in Sources */,
 				E2A0E7F218F1D12E00C580B1 /* GCDWebServerFileResponse.m in Sources */,
+				E28BAE1318F99A600095C089 /* GCDWebServerFunctions.m in Sources */,
 				E22112971690B64F0048D2B2 /* main.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;