| /* |
| Copyright (c) 2012-2015, 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) { |
| NSRange range = [value rangeOfString:@";"]; |
| return range.location != NSNotFound ? [value substringToIndex:range.location] : value; |
| } |
| |
| NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) { |
| 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]; |
| } |
| } |
| 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) { |
| 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, (__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* 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) { |
| NSString* string = nil; |
| 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) { |
| string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer]; |
| } else { |
| GWS_DNOT_REACHED(); |
| } |
| return string; |
| } |
| |
| 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) { |
| primaryInterface = [[NSString stringWithString:[(__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 || 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 stringWithUTF8String:buffer]; |
| } |