GCDWebServer.m 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320
  1. /*
  2. Copyright (c) 2012-2015, 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. #if !__has_feature(objc_arc)
  26. #error GCDWebServer requires ARC
  27. #endif
  28. #import <TargetConditionals.h>
  29. #if TARGET_OS_IPHONE
  30. #import <UIKit/UIKit.h>
  31. #else
  32. #ifdef __GCDWEBSERVER_ENABLE_TESTING__
  33. #import <AppKit/AppKit.h>
  34. #endif
  35. #endif
  36. #import <netinet/in.h>
  37. #import <dns_sd.h>
  38. #import "GCDWebServerPrivate.h"
  39. #if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
  40. #define kDefaultPort 80
  41. #else
  42. #define kDefaultPort 8080
  43. #endif
  44. #define kBonjourResolutionTimeout 5.0
  45. NSString* const GCDWebServerOption_Port = @"Port";
  46. NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
  47. NSString* const GCDWebServerOption_BonjourType = @"BonjourType";
  48. NSString* const GCDWebServerOption_RequestNATPortMapping = @"RequestNATPortMapping";
  49. NSString* const GCDWebServerOption_BindToLocalhost = @"BindToLocalhost";
  50. NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
  51. NSString* const GCDWebServerOption_ServerName = @"ServerName";
  52. NSString* const GCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod";
  53. NSString* const GCDWebServerOption_AuthenticationRealm = @"AuthenticationRealm";
  54. NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAccounts";
  55. NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
  56. NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
  57. NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
  58. #if TARGET_OS_IPHONE
  59. NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
  60. #endif
  61. NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic";
  62. NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess";
  63. #if defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
  64. #if DEBUG
  65. GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Debug;
  66. #else
  67. GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info;
  68. #endif
  69. #elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
  70. #if DEBUG
  71. DDLogLevel GCDWebServerLogLevel = DDLogLevelDebug;
  72. #else
  73. DDLogLevel GCDWebServerLogLevel = DDLogLevelInfo;
  74. #endif
  75. #endif
  76. #if !TARGET_OS_IPHONE
  77. static BOOL _run;
  78. #endif
  79. #ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
  80. void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) {
  81. static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR", "EXCEPTION"};
  82. static int enableLogging = -1;
  83. if (enableLogging < 0) {
  84. enableLogging = (isatty(STDERR_FILENO) ? 1 : 0);
  85. }
  86. if (enableLogging) {
  87. va_list arguments;
  88. va_start(arguments, format);
  89. NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
  90. va_end(arguments);
  91. fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]);
  92. }
  93. }
  94. #endif
  95. #if !TARGET_OS_IPHONE
  96. static void _SignalHandler(int signal) {
  97. _run = NO;
  98. printf("\n");
  99. }
  100. #endif
  101. #if !TARGET_OS_IPHONE || defined(__GCDWEBSERVER_ENABLE_TESTING__)
  102. // This utility function is used to ensure scheduled callbacks on the main thread are called when running the server synchronously
  103. // https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
  104. // The main queue works with the application’s run loop to interleave the execution of queued tasks with the execution of other event sources attached to the run loop
  105. // TODO: Ensure all scheduled blocks on the main queue are also executed
  106. static void _ExecuteMainThreadRunLoopSources() {
  107. SInt32 result;
  108. do {
  109. result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
  110. } while (result == kCFRunLoopRunHandledSource);
  111. }
  112. #endif
  113. @interface GCDWebServerHandler () {
  114. @private
  115. GCDWebServerMatchBlock _matchBlock;
  116. GCDWebServerAsyncProcessBlock _asyncProcessBlock;
  117. }
  118. @end
  119. @implementation GCDWebServerHandler
  120. @synthesize matchBlock=_matchBlock, asyncProcessBlock=_asyncProcessBlock;
  121. - (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
  122. if ((self = [super init])) {
  123. _matchBlock = [matchBlock copy];
  124. _asyncProcessBlock = [processBlock copy];
  125. }
  126. return self;
  127. }
  128. @end
  129. @interface GCDWebServer () {
  130. @private
  131. id<GCDWebServerDelegate> __unsafe_unretained _delegate;
  132. dispatch_queue_t _syncQueue;
  133. dispatch_group_t _sourceGroup;
  134. NSMutableArray* _handlers;
  135. NSInteger _activeConnections; // Accessed through _syncQueue only
  136. BOOL _connected; // Accessed on main thread only
  137. CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
  138. NSDictionary* _options;
  139. NSString* _serverName;
  140. NSString* _authenticationRealm;
  141. NSMutableDictionary* _authenticationBasicAccounts;
  142. NSMutableDictionary* _authenticationDigestAccounts;
  143. Class _connectionClass;
  144. BOOL _mapHEADToGET;
  145. CFTimeInterval _disconnectDelay;
  146. NSUInteger _port;
  147. dispatch_source_t _source4;
  148. dispatch_source_t _source6;
  149. CFNetServiceRef _registrationService;
  150. CFNetServiceRef _resolutionService;
  151. DNSServiceRef _dnsService;
  152. CFSocketRef _dnsSocket;
  153. CFRunLoopSourceRef _dnsSource;
  154. NSString* _dnsAddress;
  155. NSUInteger _dnsPort;
  156. BOOL _bindToLocalhost;
  157. #if TARGET_OS_IPHONE
  158. BOOL _suspendInBackground;
  159. UIBackgroundTaskIdentifier _backgroundTask;
  160. #endif
  161. #ifdef __GCDWEBSERVER_ENABLE_TESTING__
  162. BOOL _recording;
  163. #endif
  164. }
  165. @end
  166. @implementation GCDWebServer
  167. @synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm,
  168. authenticationBasicAccounts=_authenticationBasicAccounts, authenticationDigestAccounts=_authenticationDigestAccounts,
  169. shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
  170. + (void)initialize {
  171. GCDWebServerInitializeFunctions();
  172. }
  173. - (instancetype)init {
  174. if ((self = [super init])) {
  175. _syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL);
  176. _sourceGroup = dispatch_group_create();
  177. _handlers = [[NSMutableArray alloc] init];
  178. #if TARGET_OS_IPHONE
  179. _backgroundTask = UIBackgroundTaskInvalid;
  180. #endif
  181. }
  182. return self;
  183. }
  184. - (void)dealloc {
  185. GWS_DCHECK(_connected == NO);
  186. GWS_DCHECK(_activeConnections == 0);
  187. GWS_DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source
  188. GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle
  189. #if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  190. dispatch_release(_sourceGroup);
  191. dispatch_release(_syncQueue);
  192. #endif
  193. }
  194. #if TARGET_OS_IPHONE
  195. // Always called on main thread
  196. - (void)_startBackgroundTask {
  197. GWS_DCHECK([NSThread isMainThread]);
  198. if (_backgroundTask == UIBackgroundTaskInvalid) {
  199. GWS_LOG_DEBUG(@"Did start background task");
  200. _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
  201. GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
  202. [self _endBackgroundTask];
  203. }];
  204. } else {
  205. GWS_DNOT_REACHED();
  206. }
  207. }
  208. #endif
  209. // Always called on main thread
  210. - (void)_didConnect {
  211. GWS_DCHECK([NSThread isMainThread]);
  212. GWS_DCHECK(_connected == NO);
  213. _connected = YES;
  214. GWS_LOG_DEBUG(@"Did connect");
  215. #if TARGET_OS_IPHONE
  216. if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
  217. [self _startBackgroundTask];
  218. }
  219. #endif
  220. if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
  221. [_delegate webServerDidConnect:self];
  222. }
  223. }
  224. - (void)willStartConnection:(GCDWebServerConnection*)connection {
  225. dispatch_sync(_syncQueue, ^{
  226. GWS_DCHECK(_activeConnections >= 0);
  227. if (_activeConnections == 0) {
  228. dispatch_async(dispatch_get_main_queue(), ^{
  229. if (_disconnectTimer) {
  230. CFRunLoopTimerInvalidate(_disconnectTimer);
  231. CFRelease(_disconnectTimer);
  232. _disconnectTimer = NULL;
  233. }
  234. if (_connected == NO) {
  235. [self _didConnect];
  236. }
  237. });
  238. }
  239. _activeConnections += 1;
  240. });
  241. }
  242. #if TARGET_OS_IPHONE
  243. // Always called on main thread
  244. - (void)_endBackgroundTask {
  245. GWS_DCHECK([NSThread isMainThread]);
  246. if (_backgroundTask != UIBackgroundTaskInvalid) {
  247. if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source4) {
  248. [self _stop];
  249. }
  250. [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
  251. _backgroundTask = UIBackgroundTaskInvalid;
  252. GWS_LOG_DEBUG(@"Did end background task");
  253. }
  254. }
  255. #endif
  256. // Always called on main thread
  257. - (void)_didDisconnect {
  258. GWS_DCHECK([NSThread isMainThread]);
  259. GWS_DCHECK(_connected == YES);
  260. _connected = NO;
  261. GWS_LOG_DEBUG(@"Did disconnect");
  262. #if TARGET_OS_IPHONE
  263. [self _endBackgroundTask];
  264. #endif
  265. if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) {
  266. [_delegate webServerDidDisconnect:self];
  267. }
  268. }
  269. - (void)didEndConnection:(GCDWebServerConnection*)connection {
  270. dispatch_sync(_syncQueue, ^{
  271. GWS_DCHECK(_activeConnections > 0);
  272. _activeConnections -= 1;
  273. if (_activeConnections == 0) {
  274. dispatch_async(dispatch_get_main_queue(), ^{
  275. if ((_disconnectDelay > 0.0) && (_source4 != NULL)) {
  276. if (_disconnectTimer) {
  277. CFRunLoopTimerInvalidate(_disconnectTimer);
  278. CFRelease(_disconnectTimer);
  279. }
  280. _disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + _disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) {
  281. GWS_DCHECK([NSThread isMainThread]);
  282. [self _didDisconnect];
  283. CFRelease(_disconnectTimer);
  284. _disconnectTimer = NULL;
  285. });
  286. CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes);
  287. } else {
  288. [self _didDisconnect];
  289. }
  290. });
  291. }
  292. });
  293. }
  294. - (NSString*)bonjourName {
  295. CFStringRef name = _resolutionService ? CFNetServiceGetName(_resolutionService) : NULL;
  296. return name && CFStringGetLength(name) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil;
  297. }
  298. - (NSString*)bonjourType {
  299. CFStringRef type = _resolutionService ? CFNetServiceGetType(_resolutionService) : NULL;
  300. return type && CFStringGetLength(type) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil;
  301. }
  302. - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
  303. [self addHandlerWithMatchBlock:matchBlock asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
  304. completionBlock(processBlock(request));
  305. }];
  306. }
  307. - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
  308. GWS_DCHECK(_options == nil);
  309. GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock asyncProcessBlock:processBlock];
  310. [_handlers insertObject:handler atIndex:0];
  311. }
  312. - (void)removeAllHandlers {
  313. GWS_DCHECK(_options == nil);
  314. [_handlers removeAllObjects];
  315. }
  316. static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
  317. GWS_DCHECK([NSThread isMainThread]);
  318. @autoreleasepool {
  319. if (error->error) {
  320. GWS_LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain);
  321. } else {
  322. GCDWebServer* server = (__bridge GCDWebServer*)info;
  323. GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
  324. if (!CFNetServiceResolveWithTimeout(server->_resolutionService, kBonjourResolutionTimeout, NULL)) {
  325. GWS_LOG_ERROR(@"Failed starting Bonjour resolution");
  326. GWS_DNOT_REACHED();
  327. }
  328. }
  329. }
  330. }
  331. static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
  332. GWS_DCHECK([NSThread isMainThread]);
  333. @autoreleasepool {
  334. if (error->error) {
  335. if ((error->domain != kCFStreamErrorDomainNetServices) && (error->error != kCFNetServicesErrorTimeout)) {
  336. GWS_LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain);
  337. }
  338. } else {
  339. GCDWebServer* server = (__bridge GCDWebServer*)info;
  340. GWS_LOG_INFO(@"%@ now locally reachable at %@", [server class], server.bonjourServerURL);
  341. if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
  342. [server.delegate webServerDidCompleteBonjourRegistration:server];
  343. }
  344. }
  345. }
  346. }
  347. static void _DNSServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, uint32_t externalAddress, DNSServiceProtocol protocol, uint16_t internalPort, uint16_t externalPort, uint32_t ttl, void* context) {
  348. GWS_DCHECK([NSThread isMainThread]);
  349. @autoreleasepool {
  350. GCDWebServer* server = (__bridge GCDWebServer*)context;
  351. if ((errorCode == kDNSServiceErr_NoError) || (errorCode == kDNSServiceErr_DoubleNAT)) {
  352. struct sockaddr_in addr4;
  353. bzero(&addr4, sizeof(addr4));
  354. addr4.sin_len = sizeof(addr4);
  355. addr4.sin_family = AF_INET;
  356. addr4.sin_addr.s_addr = externalAddress; // Already in network byte order
  357. server->_dnsAddress = GCDWebServerStringFromSockAddr((const struct sockaddr*)&addr4, NO);
  358. server->_dnsPort = ntohs(externalPort);
  359. GWS_LOG_INFO(@"%@ now publicly reachable at %@", [server class], server.publicServerURL);
  360. } else {
  361. GWS_LOG_ERROR(@"DNS service error %i", errorCode);
  362. server->_dnsAddress = nil;
  363. server->_dnsPort = 0;
  364. }
  365. if ([server.delegate respondsToSelector:@selector(webServerDidUpdateNATPortMapping:)]) {
  366. [server.delegate webServerDidUpdateNATPortMapping:server];
  367. }
  368. }
  369. }
  370. static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) {
  371. GWS_DCHECK([NSThread isMainThread]);
  372. @autoreleasepool {
  373. GCDWebServer* server = (__bridge GCDWebServer*)info;
  374. DNSServiceErrorType status = DNSServiceProcessResult(server->_dnsService);
  375. if (status != kDNSServiceErr_NoError) {
  376. GWS_LOG_ERROR(@"DNS service error %i", status);
  377. }
  378. }
  379. }
  380. static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) {
  381. id value = [options objectForKey:key];
  382. return value ? value : defaultValue;
  383. }
  384. static inline NSString* _EncodeBase64(NSString* string) {
  385. NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding];
  386. #if (TARGET_OS_IPHONE && !(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0)) || (!TARGET_OS_IPHONE && !(__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9))
  387. if (![data respondsToSelector:@selector(base64EncodedDataWithOptions:)]) {
  388. return [data base64Encoding];
  389. }
  390. #endif
  391. return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding];
  392. }
  393. - (int)_createListeningSocket:(BOOL)useIPv6
  394. localAddress:(const void*)address
  395. length:(socklen_t)length
  396. maxPendingConnections:(NSUInteger)maxPendingConnections
  397. error:(NSError**)error {
  398. int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP);
  399. if (listeningSocket > 0) {
  400. int yes = 1;
  401. setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
  402. if (bind(listeningSocket, address, length) == 0) {
  403. if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
  404. GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket);
  405. return listeningSocket;
  406. } else {
  407. if (error) {
  408. *error = GCDWebServerMakePosixError(errno);
  409. }
  410. GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
  411. close(listeningSocket);
  412. }
  413. } else {
  414. if (error) {
  415. *error = GCDWebServerMakePosixError(errno);
  416. }
  417. GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
  418. close(listeningSocket);
  419. }
  420. } else {
  421. if (error) {
  422. *error = GCDWebServerMakePosixError(errno);
  423. }
  424. GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
  425. }
  426. return -1;
  427. }
  428. - (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 {
  429. dispatch_group_enter(_sourceGroup);
  430. dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
  431. dispatch_source_set_cancel_handler(source, ^{
  432. @autoreleasepool {
  433. int result = close(listeningSocket);
  434. if (result != 0) {
  435. GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
  436. } else {
  437. GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket);
  438. }
  439. }
  440. dispatch_group_leave(_sourceGroup);
  441. });
  442. dispatch_source_set_event_handler(source, ^{
  443. @autoreleasepool {
  444. struct sockaddr_storage remoteSockAddr;
  445. socklen_t remoteAddrLen = sizeof(remoteSockAddr);
  446. int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen);
  447. if (socket > 0) {
  448. NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
  449. struct sockaddr_storage localSockAddr;
  450. socklen_t localAddrLen = sizeof(localSockAddr);
  451. NSData* localAddress = nil;
  452. if (getsockname(socket, (struct sockaddr*)&localSockAddr, &localAddrLen) == 0) {
  453. localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
  454. GWS_DCHECK((!isIPv6 && localSockAddr.ss_family == AF_INET) || (isIPv6 && localSockAddr.ss_family == AF_INET6));
  455. } else {
  456. GWS_DNOT_REACHED();
  457. }
  458. int noSigPipe = 1;
  459. setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
  460. GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened
  461. [connection self]; // Prevent compiler from complaining about unused variable / useless statement
  462. } else {
  463. GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
  464. }
  465. }
  466. });
  467. return source;
  468. }
  469. - (BOOL)_start:(NSError**)error {
  470. GWS_DCHECK(_source4 == NULL);
  471. NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
  472. BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
  473. NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
  474. struct sockaddr_in addr4;
  475. bzero(&addr4, sizeof(addr4));
  476. addr4.sin_len = sizeof(addr4);
  477. addr4.sin_family = AF_INET;
  478. addr4.sin_port = htons(port);
  479. addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
  480. int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error];
  481. if (listeningSocket4 <= 0) {
  482. return NO;
  483. }
  484. if (port == 0) {
  485. struct sockaddr_in addr;
  486. socklen_t addrlen = sizeof(addr);
  487. if (getsockname(listeningSocket4, (struct sockaddr*)&addr, &addrlen) == 0) {
  488. port = ntohs(addr.sin_port);
  489. } else {
  490. GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
  491. }
  492. }
  493. struct sockaddr_in6 addr6;
  494. bzero(&addr6, sizeof(addr6));
  495. addr6.sin6_len = sizeof(addr6);
  496. addr6.sin6_family = AF_INET6;
  497. addr6.sin6_port = htons(port);
  498. addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any;
  499. int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error];
  500. if (listeningSocket6 <= 0) {
  501. close(listeningSocket4);
  502. return NO;
  503. }
  504. _serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
  505. NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
  506. if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
  507. _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
  508. _authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
  509. NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
  510. [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
  511. [_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
  512. }];
  513. } else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
  514. _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
  515. _authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
  516. NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
  517. [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
  518. [_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
  519. }];
  520. }
  521. _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
  522. _mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
  523. _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
  524. _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
  525. _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
  526. _port = port;
  527. _bindToLocalhost = bindToLocalhost;
  528. NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil);
  529. NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
  530. if (bonjourName) {
  531. _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
  532. if (_registrationService) {
  533. CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
  534. CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
  535. CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
  536. CFStreamError streamError = {0};
  537. CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
  538. _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
  539. if (_resolutionService) {
  540. CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
  541. CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
  542. } else {
  543. GWS_LOG_ERROR(@"Failed creating CFNetService for resolution");
  544. }
  545. } else {
  546. GWS_LOG_ERROR(@"Failed creating CFNetService for registration");
  547. }
  548. }
  549. if ([_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
  550. DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self);
  551. if (status == kDNSServiceErr_NoError) {
  552. CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
  553. _dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context);
  554. if (_dnsSocket) {
  555. CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate);
  556. _dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0);
  557. if (_dnsSource) {
  558. CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes);
  559. } else {
  560. GWS_LOG_ERROR(@"Failed creating CFRunLoopSource");
  561. GWS_DNOT_REACHED();
  562. }
  563. } else {
  564. GWS_LOG_ERROR(@"Failed creating CFSocket");
  565. GWS_DNOT_REACHED();
  566. }
  567. } else {
  568. GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status);
  569. }
  570. }
  571. dispatch_resume(_source4);
  572. dispatch_resume(_source6);
  573. GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
  574. if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
  575. dispatch_async(dispatch_get_main_queue(), ^{
  576. [_delegate webServerDidStart:self];
  577. });
  578. }
  579. return YES;
  580. }
  581. - (void)_stop {
  582. GWS_DCHECK(_source4 != NULL);
  583. if (_dnsService) {
  584. _dnsAddress = nil;
  585. _dnsPort = 0;
  586. if (_dnsSource) {
  587. CFRunLoopSourceInvalidate(_dnsSource);
  588. CFRelease(_dnsSource);
  589. _dnsSource = NULL;
  590. }
  591. if (_dnsSocket) {
  592. CFRelease(_dnsSocket);
  593. _dnsSocket = NULL;
  594. }
  595. DNSServiceRefDeallocate(_dnsService);
  596. _dnsService = NULL;
  597. }
  598. if (_registrationService) {
  599. if (_resolutionService) {
  600. CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
  601. CFNetServiceSetClient(_resolutionService, NULL, NULL);
  602. CFNetServiceCancel(_resolutionService);
  603. CFRelease(_resolutionService);
  604. _resolutionService = NULL;
  605. }
  606. CFNetServiceUnscheduleFromRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
  607. CFNetServiceSetClient(_registrationService, NULL, NULL);
  608. CFNetServiceCancel(_registrationService);
  609. CFRelease(_registrationService);
  610. _registrationService = NULL;
  611. }
  612. dispatch_source_cancel(_source6);
  613. dispatch_source_cancel(_source4);
  614. dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed
  615. #if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  616. dispatch_release(_source6);
  617. #endif
  618. _source6 = NULL;
  619. #if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  620. dispatch_release(_source4);
  621. #endif
  622. _source4 = NULL;
  623. _port = 0;
  624. _bindToLocalhost = NO;
  625. _serverName = nil;
  626. _authenticationRealm = nil;
  627. _authenticationBasicAccounts = nil;
  628. _authenticationDigestAccounts = nil;
  629. dispatch_async(dispatch_get_main_queue(), ^{
  630. if (_disconnectTimer) {
  631. CFRunLoopTimerInvalidate(_disconnectTimer);
  632. CFRelease(_disconnectTimer);
  633. _disconnectTimer = NULL;
  634. [self _didDisconnect];
  635. }
  636. });
  637. GWS_LOG_INFO(@"%@ stopped", [self class]);
  638. if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
  639. dispatch_async(dispatch_get_main_queue(), ^{
  640. [_delegate webServerDidStop:self];
  641. });
  642. }
  643. }
  644. #if TARGET_OS_IPHONE
  645. - (void)_didEnterBackground:(NSNotification*)notification {
  646. GWS_DCHECK([NSThread isMainThread]);
  647. GWS_LOG_DEBUG(@"Did enter background");
  648. if ((_backgroundTask == UIBackgroundTaskInvalid) && _source4) {
  649. [self _stop];
  650. }
  651. }
  652. - (void)_willEnterForeground:(NSNotification*)notification {
  653. GWS_DCHECK([NSThread isMainThread]);
  654. GWS_LOG_DEBUG(@"Will enter foreground");
  655. if (!_source4) {
  656. [self _start:NULL]; // TODO: There's probably nothing we can do on failure
  657. }
  658. }
  659. #endif
  660. - (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
  661. if (_options == nil) {
  662. _options = options ? [options copy] : @{};
  663. #if TARGET_OS_IPHONE
  664. _suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
  665. if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error])
  666. #else
  667. if (![self _start:error])
  668. #endif
  669. {
  670. _options = nil;
  671. return NO;
  672. }
  673. #if TARGET_OS_IPHONE
  674. if (_suspendInBackground) {
  675. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
  676. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
  677. }
  678. #endif
  679. return YES;
  680. } else {
  681. GWS_DNOT_REACHED();
  682. }
  683. return NO;
  684. }
  685. - (BOOL)isRunning {
  686. return (_options ? YES : NO);
  687. }
  688. - (void)stop {
  689. if (_options) {
  690. #if TARGET_OS_IPHONE
  691. if (_suspendInBackground) {
  692. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
  693. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
  694. }
  695. #endif
  696. if (_source4) {
  697. [self _stop];
  698. }
  699. _options = nil;
  700. } else {
  701. GWS_DNOT_REACHED();
  702. }
  703. }
  704. @end
  705. @implementation GCDWebServer (Extensions)
  706. - (NSURL*)serverURL {
  707. if (_source4) {
  708. NSString* ipAddress = _bindToLocalhost ? @"localhost" : GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
  709. if (ipAddress) {
  710. if (_port != 80) {
  711. return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]];
  712. } else {
  713. return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", ipAddress]];
  714. }
  715. }
  716. }
  717. return nil;
  718. }
  719. - (NSURL*)bonjourServerURL {
  720. if (_source4 && _resolutionService) {
  721. NSString* name = (__bridge NSString*)CFNetServiceGetTargetHost(_resolutionService);
  722. if (name.length) {
  723. name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain
  724. if (_port != 80) {
  725. return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", name, (int)_port]];
  726. } else {
  727. return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", name]];
  728. }
  729. }
  730. }
  731. return nil;
  732. }
  733. - (NSURL*)publicServerURL {
  734. if (_source4 && _dnsService && _dnsAddress && _dnsPort) {
  735. if (_dnsPort != 80) {
  736. return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", _dnsAddress, (int)_dnsPort]];
  737. } else {
  738. return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", _dnsAddress]];
  739. }
  740. }
  741. return nil;
  742. }
  743. - (BOOL)start {
  744. return [self startWithPort:kDefaultPort bonjourName:@""];
  745. }
  746. - (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
  747. NSMutableDictionary* options = [NSMutableDictionary dictionary];
  748. [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
  749. [options setValue:name forKey:GCDWebServerOption_BonjourName];
  750. return [self startWithOptions:options error:NULL];
  751. }
  752. #if !TARGET_OS_IPHONE
  753. - (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name {
  754. NSMutableDictionary* options = [NSMutableDictionary dictionary];
  755. [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
  756. [options setValue:name forKey:GCDWebServerOption_BonjourName];
  757. return [self runWithOptions:options error:NULL];
  758. }
  759. - (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error {
  760. GWS_DCHECK([NSThread isMainThread]);
  761. BOOL success = NO;
  762. _run = YES;
  763. void (*termHandler)(int) = signal(SIGTERM, _SignalHandler);
  764. void (*intHandler)(int) = signal(SIGINT, _SignalHandler);
  765. if ((termHandler != SIG_ERR) && (intHandler != SIG_ERR)) {
  766. if ([self startWithOptions:options error:error]) {
  767. while (_run) {
  768. CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
  769. }
  770. [self stop];
  771. success = YES;
  772. }
  773. _ExecuteMainThreadRunLoopSources();
  774. signal(SIGINT, intHandler);
  775. signal(SIGTERM, termHandler);
  776. }
  777. return success;
  778. }
  779. #endif
  780. @end
  781. @implementation GCDWebServer (Handlers)
  782. - (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
  783. [self addDefaultHandlerForMethod:method requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
  784. completionBlock(block(request));
  785. }];
  786. }
  787. - (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
  788. [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
  789. if (![requestMethod isEqualToString:method]) {
  790. return nil;
  791. }
  792. return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
  793. } asyncProcessBlock:block];
  794. }
  795. - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
  796. [self addHandlerForMethod:method path:path requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
  797. completionBlock(block(request));
  798. }];
  799. }
  800. - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
  801. if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
  802. [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
  803. if (![requestMethod isEqualToString:method]) {
  804. return nil;
  805. }
  806. if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
  807. return nil;
  808. }
  809. return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
  810. } asyncProcessBlock:block];
  811. } else {
  812. GWS_DNOT_REACHED();
  813. }
  814. }
  815. - (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
  816. [self addHandlerForMethod:method pathRegex:regex requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
  817. completionBlock(block(request));
  818. }];
  819. }
  820. - (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
  821. NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
  822. if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
  823. [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
  824. if (![requestMethod isEqualToString:method]) {
  825. return nil;
  826. }
  827. NSArray* matches = [expression matchesInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)];
  828. if (matches.count == 0) {
  829. return nil;
  830. }
  831. NSMutableArray* captures = [NSMutableArray array];
  832. for (NSTextCheckingResult* result in matches) {
  833. // Start at 1; index 0 is the whole string
  834. for (NSUInteger i = 1; i < result.numberOfRanges; i++) {
  835. [captures addObject:[urlPath substringWithRange:[result rangeAtIndex:i]]];
  836. }
  837. }
  838. GCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
  839. [request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
  840. return request;
  841. } asyncProcessBlock:block];
  842. } else {
  843. GWS_DNOT_REACHED();
  844. }
  845. }
  846. @end
  847. @implementation GCDWebServer (GETHandlers)
  848. - (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
  849. [self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
  850. GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
  851. response.cacheControlMaxAge = cacheAge;
  852. return response;
  853. }];
  854. }
  855. - (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
  856. [self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
  857. GCDWebServerResponse* response = nil;
  858. if (allowRangeRequests) {
  859. response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
  860. [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
  861. } else {
  862. response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment];
  863. }
  864. response.cacheControlMaxAge = cacheAge;
  865. return response;
  866. }];
  867. }
  868. - (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
  869. NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
  870. if (enumerator == nil) {
  871. return nil;
  872. }
  873. NSMutableString* html = [NSMutableString string];
  874. [html appendString:@"<!DOCTYPE html>\n"];
  875. [html appendString:@"<html><head><meta charset=\"utf-8\"></head><body>\n"];
  876. [html appendString:@"<ul>\n"];
  877. for (NSString* file in enumerator) {
  878. if (![file hasPrefix:@"."]) {
  879. NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType];
  880. #pragma clang diagnostic push
  881. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  882. NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  883. #pragma clang diagnostic pop
  884. GWS_DCHECK(escapedFile);
  885. if ([type isEqualToString:NSFileTypeRegular]) {
  886. [html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, file];
  887. } else if ([type isEqualToString:NSFileTypeDirectory]) {
  888. [html appendFormat:@"<li><a href=\"%@/\">%@/</a></li>\n", escapedFile, file];
  889. }
  890. }
  891. [enumerator skipDescendents];
  892. }
  893. [html appendString:@"</ul>\n"];
  894. [html appendString:@"</body></html>\n"];
  895. return [GCDWebServerDataResponse responseWithHTML:html];
  896. }
  897. - (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
  898. if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
  899. GCDWebServer* __unsafe_unretained server = self;
  900. [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
  901. if (![requestMethod isEqualToString:@"GET"]) {
  902. return nil;
  903. }
  904. if (![urlPath hasPrefix:basePath]) {
  905. return nil;
  906. }
  907. return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
  908. } processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
  909. GCDWebServerResponse* response = nil;
  910. NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
  911. NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType];
  912. if (fileType) {
  913. if ([fileType isEqualToString:NSFileTypeDirectory]) {
  914. if (indexFilename) {
  915. NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
  916. NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType];
  917. if ([indexType isEqualToString:NSFileTypeRegular]) {
  918. return [GCDWebServerFileResponse responseWithFile:indexPath];
  919. }
  920. }
  921. response = [server _responseWithContentsOfDirectory:filePath];
  922. } else if ([fileType isEqualToString:NSFileTypeRegular]) {
  923. if (allowRangeRequests) {
  924. response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange];
  925. [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
  926. } else {
  927. response = [GCDWebServerFileResponse responseWithFile:filePath];
  928. }
  929. }
  930. }
  931. if (response) {
  932. response.cacheControlMaxAge = cacheAge;
  933. } else {
  934. response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];
  935. }
  936. return response;
  937. }];
  938. } else {
  939. GWS_DNOT_REACHED();
  940. }
  941. }
  942. @end
  943. @implementation GCDWebServer (Logging)
  944. + (void)setLogLevel:(int)level {
  945. #if defined(__GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__)
  946. [XLSharedFacility setMinLogLevel:level];
  947. #elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
  948. GCDWebServerLogLevel = level;
  949. #elif defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
  950. GCDWebServerLogLevel = level;
  951. #endif
  952. }
  953. - (void)logVerbose:(NSString*)format, ... {
  954. va_list arguments;
  955. va_start(arguments, format);
  956. GWS_LOG_VERBOSE(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]);
  957. va_end(arguments);
  958. }
  959. - (void)logInfo:(NSString*)format, ... {
  960. va_list arguments;
  961. va_start(arguments, format);
  962. GWS_LOG_INFO(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]);
  963. va_end(arguments);
  964. }
  965. - (void)logWarning:(NSString*)format, ... {
  966. va_list arguments;
  967. va_start(arguments, format);
  968. GWS_LOG_WARNING(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]);
  969. va_end(arguments);
  970. }
  971. - (void)logError:(NSString*)format, ... {
  972. va_list arguments;
  973. va_start(arguments, format);
  974. GWS_LOG_ERROR(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]);
  975. va_end(arguments);
  976. }
  977. - (void)logException:(NSException*)exception {
  978. GWS_LOG_EXCEPTION(exception);
  979. }
  980. @end
  981. #ifdef __GCDWEBSERVER_ENABLE_TESTING__
  982. @implementation GCDWebServer (Testing)
  983. - (void)setRecordingEnabled:(BOOL)flag {
  984. _recording = flag;
  985. }
  986. - (BOOL)isRecordingEnabled {
  987. return _recording;
  988. }
  989. static CFHTTPMessageRef _CreateHTTPMessageFromData(NSData* data, BOOL isRequest) {
  990. CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, isRequest);
  991. if (CFHTTPMessageAppendBytes(message, data.bytes, data.length)) {
  992. return message;
  993. }
  994. CFRelease(message);
  995. return NULL;
  996. }
  997. static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData, NSUInteger port) {
  998. CFHTTPMessageRef response = NULL;
  999. int httpSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  1000. if (httpSocket > 0) {
  1001. struct sockaddr_in addr4;
  1002. bzero(&addr4, sizeof(addr4));
  1003. addr4.sin_len = sizeof(port);
  1004. addr4.sin_family = AF_INET;
  1005. addr4.sin_port = htons(8080);
  1006. addr4.sin_addr.s_addr = htonl(INADDR_ANY);
  1007. if (connect(httpSocket, (void*)&addr4, sizeof(addr4)) == 0) {
  1008. if (write(httpSocket, inData.bytes, inData.length) == (ssize_t)inData.length) {
  1009. NSMutableData* outData = [[NSMutableData alloc] initWithLength:(256 * 1024)];
  1010. NSUInteger length = 0;
  1011. while (1) {
  1012. ssize_t result = read(httpSocket, (char*)outData.mutableBytes + length, outData.length - length);
  1013. if (result < 0) {
  1014. length = NSUIntegerMax;
  1015. break;
  1016. } else if (result == 0) {
  1017. break;
  1018. }
  1019. length += result;
  1020. if (length >= outData.length) {
  1021. outData.length = 2 * outData.length;
  1022. }
  1023. }
  1024. if (length != NSUIntegerMax) {
  1025. outData.length = length;
  1026. response = _CreateHTTPMessageFromData(outData, NO);
  1027. } else {
  1028. GWS_DNOT_REACHED();
  1029. }
  1030. }
  1031. }
  1032. close(httpSocket);
  1033. }
  1034. return response;
  1035. }
  1036. static void _LogResult(NSString* format, ...) {
  1037. va_list arguments;
  1038. va_start(arguments, format);
  1039. NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
  1040. va_end(arguments);
  1041. fprintf(stdout, "%s\n", [message UTF8String]);
  1042. }
  1043. - (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path {
  1044. GWS_DCHECK([NSThread isMainThread]);
  1045. NSArray* ignoredHeaders = @[@"Date", @"Etag"]; // Dates are always different by definition and ETags depend on file system node IDs
  1046. NSInteger result = -1;
  1047. if ([self startWithOptions:options error:NULL]) {
  1048. _ExecuteMainThreadRunLoopSources();
  1049. result = 0;
  1050. NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
  1051. for (NSString* requestFile in files) {
  1052. if (![requestFile hasSuffix:@".request"]) {
  1053. continue;
  1054. }
  1055. @autoreleasepool {
  1056. NSString* index = [[requestFile componentsSeparatedByString:@"-"] firstObject];
  1057. BOOL success = NO;
  1058. NSData* requestData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:requestFile]];
  1059. if (requestData) {
  1060. CFHTTPMessageRef request = _CreateHTTPMessageFromData(requestData, YES);
  1061. if (request) {
  1062. NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(request));
  1063. NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(request));
  1064. _LogResult(@"[%i] %@ %@", (int)[index integerValue], requestMethod, requestURL.path);
  1065. NSString* prefix = [index stringByAppendingString:@"-"];
  1066. for (NSString* responseFile in files) {
  1067. if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) {
  1068. NSData* responseData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:responseFile]];
  1069. if (responseData) {
  1070. CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO);
  1071. if (expectedResponse) {
  1072. CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, self.port);
  1073. if (actualResponse) {
  1074. success = YES;
  1075. CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse);
  1076. CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse);
  1077. if (actualStatusCode != expectedStatusCode) {
  1078. _LogResult(@" Status code not matching:\n Expected: %i\n Actual: %i", (int)expectedStatusCode, (int)actualStatusCode);
  1079. success = NO;
  1080. }
  1081. NSDictionary* expectedHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(expectedResponse));
  1082. NSDictionary* actualHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(actualResponse));
  1083. for (NSString* expectedHeader in expectedHeaders) {
  1084. if ([ignoredHeaders containsObject:expectedHeader]) {
  1085. continue;
  1086. }
  1087. NSString* expectedValue = [expectedHeaders objectForKey:expectedHeader];
  1088. NSString* actualValue = [actualHeaders objectForKey:expectedHeader];
  1089. if (![actualValue isEqualToString:expectedValue]) {
  1090. _LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", expectedHeader, expectedValue, actualValue);
  1091. success = NO;
  1092. }
  1093. }
  1094. for (NSString* actualHeader in actualHeaders) {
  1095. if (![expectedHeaders objectForKey:actualHeader]) {
  1096. _LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", actualHeader, nil, [actualHeaders objectForKey:actualHeader]);
  1097. success = NO;
  1098. }
  1099. }
  1100. NSString* expectedContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(expectedResponse, CFSTR("Content-Length")));
  1101. NSData* expectedBody = CFBridgingRelease(CFHTTPMessageCopyBody(expectedResponse));
  1102. NSString* actualContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(actualResponse, CFSTR("Content-Length")));
  1103. NSData* actualBody = CFBridgingRelease(CFHTTPMessageCopyBody(actualResponse));
  1104. if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) { // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file)
  1105. actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)];
  1106. }
  1107. if (![actualBody isEqualToData:expectedBody]) {
  1108. _LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
  1109. success = NO;
  1110. #if !TARGET_OS_IPHONE
  1111. #if DEBUG
  1112. if (GCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
  1113. NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
  1114. NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
  1115. if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {
  1116. NSTask* task = [[NSTask alloc] init];
  1117. [task setLaunchPath:@"/usr/bin/opendiff"];
  1118. [task setArguments:@[expectedPath, actualPath]];
  1119. [task launch];
  1120. }
  1121. }
  1122. #endif
  1123. #endif
  1124. }
  1125. CFRelease(actualResponse);
  1126. }
  1127. CFRelease(expectedResponse);
  1128. }
  1129. } else {
  1130. GWS_DNOT_REACHED();
  1131. }
  1132. break;
  1133. }
  1134. }
  1135. CFRelease(request);
  1136. }
  1137. } else {
  1138. GWS_DNOT_REACHED();
  1139. }
  1140. _LogResult(@"");
  1141. if (!success) {
  1142. ++result;
  1143. }
  1144. }
  1145. _ExecuteMainThreadRunLoopSources();
  1146. }
  1147. [self stop];
  1148. _ExecuteMainThreadRunLoopSources();
  1149. }
  1150. return result;
  1151. }
  1152. @end
  1153. #endif