GCDWebServer.m 53 KB

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