123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- /*
- Copyright (c) 2012-2019, 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.
- */
- #if !__has_feature(objc_arc)
- #error GCDWebServer requires ARC
- #endif
- #import <TargetConditionals.h>
- #if TARGET_OS_IPHONE
- #import <MobileCoreServices/MobileCoreServices.h>
- #else
- #import <SystemConfiguration/SystemConfiguration.h>
- #endif
- #import <CommonCrypto/CommonDigest.h>
- #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;
- // TODO: Handle RFC 850 and ANSI C's asctime() format
- void GCDWebServerInitializeFunctions() {
- GWS_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 = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
- GWS_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 = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
- GWS_DCHECK(_dateFormatterISO8601);
- }
- if (_dateFormatterQueue == NULL) {
- _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
- GWS_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) {
- if (value) {
- NSRange range = [value rangeOfString:@";"];
- if (range.location != NSNotFound) {
- return [value substringToIndex:range.location];
- }
- }
- return value;
- }
- NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
- NSString* parameter = nil;
- if (value) {
- 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];
- }
- }
- }
- 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 string;
- }
- }
- return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
- }
- NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary<NSString*, NSString*>* overrides) {
- NSDictionary* builtInOverrides = @{@"css" : @"text/css"};
- NSString* mimeType = nil;
- extension = [extension lowercaseString];
- if (extension.length) {
- mimeType = [overrides objectForKey:extension];
- if (mimeType == nil) {
- mimeType = [builtInOverrides objectForKey:extension];
- }
- if (mimeType == nil) {
- CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
- if (uti) {
- mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
- CFRelease(uti);
- }
- }
- }
- return mimeType ? mimeType : kGCDWebServerDefaultMimeType;
- }
- NSString* GCDWebServerEscapeURLString(NSString* string) {
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
- #pragma clang diagnostic pop
- }
- NSString* GCDWebServerUnescapeURLString(NSString* string) {
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
- #pragma clang diagnostic pop
- }
- NSDictionary<NSString*, NSString*>* 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;
- [scanner scanUpToString:@"&" intoString:&value];
- if (value == nil) {
- value = @"";
- }
- key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
- NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil;
- value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
- NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(value) : nil;
- if (unescapedKey && unescapedValue) {
- [parameters setObject:unescapedValue forKey:unescapedKey];
- } else {
- GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value);
- GWS_DNOT_REACHED();
- }
- if ([scanner isAtEnd]) {
- break;
- }
- [scanner setScanLocation:([scanner scanLocation] + 1)];
- }
- return parameters;
- }
- NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
- char hostBuffer[NI_MAXHOST];
- char serviceBuffer[NI_MAXSERV];
- if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) {
- #if DEBUG
- GWS_DNOT_REACHED();
- #else
- return @"";
- #endif
- }
- return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer];
- }
- NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
- NSString* address = nil;
- #if TARGET_OS_IPHONE
- #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV
- 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")); // There is no equivalent for IPv6 but the primary interface should be the same
- if (info) {
- NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"];
- if (interface) {
- primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool
- }
- 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 || TARGET_OS_TV
- // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
- // Assumption holds for Apple TV running tvOS
- if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))
- #else
- if (strcmp(ifap->ifa_name, primaryInterface))
- #endif
- {
- continue;
- }
- if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) {
- address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO);
- break;
- }
- }
- freeifaddrs(list);
- }
- return address;
- }
- NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
- va_list arguments;
- va_start(arguments, format);
- const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String];
- va_end(arguments);
- unsigned char md5[CC_MD5_DIGEST_LENGTH];
- CC_MD5(string, (CC_LONG)strlen(string), md5);
- char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
- for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
- unsigned char byte = md5[i];
- unsigned char byteHi = (byte & 0xF0) >> 4;
- buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi;
- unsigned char byteLo = byte & 0x0F;
- buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
- }
- buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
- return (NSString*)[NSString stringWithUTF8String:buffer];
- }
- NSString* GCDWebServerNormalizePath(NSString* path) {
- NSMutableArray* components = [[NSMutableArray alloc] init];
- for (NSString* component in [path componentsSeparatedByString:@"/"]) {
- if ([component isEqualToString:@".."]) {
- [components removeLastObject];
- } else if (component.length && ![component isEqualToString:@"."]) {
- [components addObject:component];
- }
- }
- if (path.length && ([path characterAtIndex:0] == '/')) {
- return [@"/" stringByAppendingString:[components componentsJoinedByString:@"/"]]; // Preserve initial slash
- }
- return [components componentsJoinedByString:@"/"];
- }
|