GCDWebServerFunctions.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /*
  2. Copyright (c) 2012-2014, Pierre-Olivier Latour
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are met:
  6. * Redistributions of source code must retain the above copyright
  7. notice, this list of conditions and the following disclaimer.
  8. * Redistributions in binary form must reproduce the above copyright
  9. notice, this list of conditions and the following disclaimer in the
  10. documentation and/or other materials provided with the distribution.
  11. * The name of Pierre-Olivier Latour may not be used to endorse
  12. or promote products derived from this software without specific
  13. prior written permission.
  14. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  15. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  16. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17. DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
  18. DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  19. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  20. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  21. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  23. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. #import <TargetConditionals.h>
  26. #if TARGET_OS_IPHONE
  27. #import <MobileCoreServices/MobileCoreServices.h>
  28. #else
  29. #import <SystemConfiguration/SystemConfiguration.h>
  30. #endif
  31. #import <CommonCrypto/CommonDigest.h>
  32. #import <ifaddrs.h>
  33. #import <net/if.h>
  34. #import <netdb.h>
  35. #import "GCDWebServerPrivate.h"
  36. static NSDateFormatter* _dateFormatterRFC822 = nil;
  37. static NSDateFormatter* _dateFormatterISO8601 = nil;
  38. static dispatch_queue_t _dateFormatterQueue = NULL;
  39. // TODO: Handle RFC 850 and ANSI C's asctime() format
  40. void GCDWebServerInitializeFunctions() {
  41. DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
  42. if (_dateFormatterRFC822 == nil) {
  43. _dateFormatterRFC822 = [[NSDateFormatter alloc] init];
  44. _dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
  45. _dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
  46. _dateFormatterRFC822.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
  47. DCHECK(_dateFormatterRFC822);
  48. }
  49. if (_dateFormatterISO8601 == nil) {
  50. _dateFormatterISO8601 = [[NSDateFormatter alloc] init];
  51. _dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
  52. _dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
  53. _dateFormatterISO8601.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
  54. DCHECK(_dateFormatterISO8601);
  55. }
  56. if (_dateFormatterQueue == NULL) {
  57. _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
  58. DCHECK(_dateFormatterQueue);
  59. }
  60. }
  61. NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
  62. if (value) {
  63. NSRange range = [value rangeOfString:@";"]; // Assume part before ";" separator is case-insensitive
  64. if (range.location != NSNotFound) {
  65. value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]];
  66. } else {
  67. value = [value lowercaseString];
  68. }
  69. }
  70. return value;
  71. }
  72. NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
  73. NSRange range = [value rangeOfString:@";"];
  74. return range.location != NSNotFound ? [value substringToIndex:range.location] : value;
  75. }
  76. NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
  77. NSString* parameter = nil;
  78. NSScanner* scanner = [[NSScanner alloc] initWithString:value];
  79. [scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
  80. NSString* string = [NSString stringWithFormat:@"%@=", name];
  81. if ([scanner scanUpToString:string intoString:NULL]) {
  82. [scanner scanString:string intoString:NULL];
  83. if ([scanner scanString:@"\"" intoString:NULL]) {
  84. [scanner scanUpToString:@"\"" intoString:&parameter];
  85. } else {
  86. [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
  87. }
  88. }
  89. ARC_RELEASE(scanner);
  90. return parameter;
  91. }
  92. // http://www.w3schools.com/tags/ref_charactersets.asp
  93. NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) {
  94. NSStringEncoding encoding = kCFStringEncodingInvalidId;
  95. if (charset) {
  96. encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
  97. }
  98. return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
  99. }
  100. NSString* GCDWebServerFormatRFC822(NSDate* date) {
  101. __block NSString* string;
  102. dispatch_sync(_dateFormatterQueue, ^{
  103. string = [_dateFormatterRFC822 stringFromDate:date];
  104. });
  105. return string;
  106. }
  107. NSDate* GCDWebServerParseRFC822(NSString* string) {
  108. __block NSDate* date;
  109. dispatch_sync(_dateFormatterQueue, ^{
  110. date = [_dateFormatterRFC822 dateFromString:string];
  111. });
  112. return date;
  113. }
  114. NSString* GCDWebServerFormatISO8601(NSDate* date) {
  115. __block NSString* string;
  116. dispatch_sync(_dateFormatterQueue, ^{
  117. string = [_dateFormatterISO8601 stringFromDate:date];
  118. });
  119. return string;
  120. }
  121. NSDate* GCDWebServerParseISO8601(NSString* string) {
  122. __block NSDate* date;
  123. dispatch_sync(_dateFormatterQueue, ^{
  124. date = [_dateFormatterISO8601 dateFromString:string];
  125. });
  126. return date;
  127. }
  128. BOOL GCDWebServerIsTextContentType(NSString* type) {
  129. return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]);
  130. }
  131. NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
  132. if (GCDWebServerIsTextContentType(type)) {
  133. NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
  134. NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
  135. if (string) {
  136. return ARC_AUTORELEASE(string);
  137. }
  138. }
  139. return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
  140. }
  141. NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
  142. static NSDictionary* _overrides = nil;
  143. if (_overrides == nil) {
  144. _overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
  145. @"text/css", @"css",
  146. nil];
  147. }
  148. NSString* mimeType = nil;
  149. extension = [extension lowercaseString];
  150. if (extension.length) {
  151. mimeType = [_overrides objectForKey:extension];
  152. if (mimeType == nil) {
  153. CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (ARC_BRIDGE CFStringRef)extension, NULL);
  154. if (uti) {
  155. mimeType = ARC_BRIDGE_RELEASE(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
  156. CFRelease(uti);
  157. }
  158. }
  159. }
  160. return mimeType ? mimeType : kGCDWebServerDefaultMimeType;
  161. }
  162. NSString* GCDWebServerEscapeURLString(NSString* string) {
  163. return ARC_BRIDGE_RELEASE(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
  164. }
  165. NSString* GCDWebServerUnescapeURLString(NSString* string) {
  166. return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
  167. }
  168. NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
  169. NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
  170. NSScanner* scanner = [[NSScanner alloc] initWithString:form];
  171. [scanner setCharactersToBeSkipped:nil];
  172. while (1) {
  173. NSString* key = nil;
  174. if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) {
  175. break;
  176. }
  177. [scanner setScanLocation:([scanner scanLocation] + 1)];
  178. NSString* value = nil;
  179. [scanner scanUpToString:@"&" intoString:&value];
  180. if (value == nil) {
  181. value = @"";
  182. }
  183. key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
  184. value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
  185. if (key && value) {
  186. [parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
  187. } else {
  188. DNOT_REACHED();
  189. }
  190. if ([scanner isAtEnd]) {
  191. break;
  192. }
  193. [scanner setScanLocation:([scanner scanLocation] + 1)];
  194. }
  195. ARC_RELEASE(scanner);
  196. return parameters;
  197. }
  198. NSString* GCDWebServerGetPrimaryIPv4Address() {
  199. NSString* address = nil;
  200. #if TARGET_OS_IPHONE
  201. #if !TARGET_IPHONE_SIMULATOR
  202. const char* primaryInterface = "en0"; // WiFi interface on iOS
  203. #endif
  204. #else
  205. const char* primaryInterface = NULL;
  206. SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
  207. if (store) {
  208. CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4"));
  209. if (info) {
  210. primaryInterface = [[NSString stringWithString:[(ARC_BRIDGE NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
  211. CFRelease(info);
  212. }
  213. CFRelease(store);
  214. }
  215. if (primaryInterface == NULL) {
  216. primaryInterface = "lo0";
  217. }
  218. #endif
  219. struct ifaddrs* list;
  220. if (getifaddrs(&list) >= 0) {
  221. for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
  222. #if TARGET_IPHONE_SIMULATOR
  223. 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
  224. #else
  225. if (strcmp(ifap->ifa_name, primaryInterface))
  226. #endif
  227. {
  228. continue;
  229. }
  230. if ((ifap->ifa_flags & IFF_UP) && (ifap->ifa_addr->sa_family == AF_INET)) {
  231. char buffer[NI_MAXHOST];
  232. if (getnameinfo(ifap->ifa_addr, ifap->ifa_addr->sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST | NI_NOFQDN) >= 0) {
  233. address = [NSString stringWithUTF8String:buffer];
  234. }
  235. break;
  236. }
  237. }
  238. freeifaddrs(list);
  239. }
  240. return address;
  241. }
  242. NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
  243. va_list arguments;
  244. va_start(arguments, format);
  245. const char* string = [ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]) UTF8String];
  246. va_end(arguments);
  247. unsigned char md5[CC_MD5_DIGEST_LENGTH];
  248. CC_MD5(string, (CC_LONG)strlen(string), md5);
  249. char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
  250. for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
  251. unsigned char byte = md5[i];
  252. unsigned char byteHi = (byte & 0xF0) >> 4;
  253. buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi;
  254. unsigned char byteLo = byte & 0x0F;
  255. buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
  256. }
  257. buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
  258. return [NSString stringWithUTF8String:buffer];
  259. }