diff --git a/CGDWebServer/GCDWebServer.h b/CGDWebServer/GCDWebServer.h index d330ad3..7ab6f76 100644 --- a/CGDWebServer/GCDWebServer.h +++ b/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; diff --git a/CGDWebServer/GCDWebServer.m b/CGDWebServer/GCDWebServer.m index 88ab6c7..e373413 100644 --- a/CGDWebServer/GCDWebServer.m +++ b/CGDWebServer/GCDWebServer.m @@ -26,16 +26,7 @@ */ #import -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - #import -#import -#import -#import #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:¶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; - 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]) { diff --git a/CGDWebServer/GCDWebServerFunctions.h b/CGDWebServer/GCDWebServerFunctions.h new file mode 100644 index 0000000..8c896b0 --- /dev/null +++ b/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 + +#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 diff --git a/CGDWebServer/GCDWebServerFunctions.m b/CGDWebServer/GCDWebServerFunctions.m new file mode 100644 index 0000000..86906c3 --- /dev/null +++ b/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 +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#import +#import +#import + +#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:¶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; + 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; +} diff --git a/CGDWebServer/GCDWebServerPrivate.h b/CGDWebServer/GCDWebServerPrivate.h index 88efee6..0661644 100644 --- a/CGDWebServer/GCDWebServerPrivate.h +++ b/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 () diff --git a/GCDWebDAVServer/GCDWebDAVServer.m b/GCDWebDAVServer/GCDWebDAVServer.m index 28c32b4..04ae25f 100644 --- a/GCDWebDAVServer/GCDWebDAVServer.m +++ b/GCDWebDAVServer/GCDWebDAVServer.m @@ -32,6 +32,8 @@ #import "GCDWebDAVServer.h" +#import "GCDWebServerFunctions.h" + #import "GCDWebServerDataRequest.h" #import "GCDWebServerFileRequest.h" diff --git a/GCDWebServer.xcodeproj/project.pbxproj b/GCDWebServer.xcodeproj/project.pbxproj index b3ea72a..b85b723 100644 --- a/GCDWebServer.xcodeproj/project.pbxproj +++ b/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 = ""; }; E276647B18F3BC2100A034BA /* GCDWebServerErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerErrorResponse.m; sourceTree = ""; }; + E28BAE1018F99A600095C089 /* GCDWebServerFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFunctions.h; sourceTree = ""; }; + E28BAE1118F99A600095C089 /* GCDWebServerFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFunctions.m; sourceTree = ""; }; E2A0E7EB18F1D03700C580B1 /* GCDWebServerDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataResponse.h; sourceTree = ""; }; E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataResponse.m; sourceTree = ""; }; E2A0E7EF18F1D12E00C580B1 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = ""; }; @@ -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;