diff --git a/GCDWebServer/Core/GCDWebServer.m b/GCDWebServer/Core/GCDWebServer.m index 1810078..9ec0fee 100644 --- a/GCDWebServer/Core/GCDWebServer.m +++ b/GCDWebServer/Core/GCDWebServer.m @@ -155,7 +155,7 @@ static void _ExecuteMainThreadRunLoopSources() { @private id __unsafe_unretained _delegate; dispatch_queue_t _syncQueue; - dispatch_semaphore_t _sourceSemaphore; + dispatch_group_t _sourceGroup; NSMutableArray* _handlers; NSInteger _activeConnections; // Accessed through _syncQueue only BOOL _connected; // Accessed on main thread only @@ -170,7 +170,8 @@ static void _ExecuteMainThreadRunLoopSources() { BOOL _mapHEADToGET; CFTimeInterval _disconnectDelay; NSUInteger _port; - dispatch_source_t _source; + dispatch_source_t _source4; + dispatch_source_t _source6; CFNetServiceRef _registrationService; CFNetServiceRef _resolutionService; #if TARGET_OS_IPHONE @@ -196,7 +197,7 @@ static void _ExecuteMainThreadRunLoopSources() { - (instancetype)init { if ((self = [super init])) { _syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL); - _sourceSemaphore = dispatch_semaphore_create(0); + _sourceGroup = dispatch_group_create(); _handlers = [[NSMutableArray alloc] init]; #if TARGET_OS_IPHONE _backgroundTask = UIBackgroundTaskInvalid; @@ -212,7 +213,7 @@ static void _ExecuteMainThreadRunLoopSources() { GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle ARC_RELEASE(_handlers); - ARC_DISPATCH_RELEASE(_sourceSemaphore); + ARC_DISPATCH_RELEASE(_sourceGroup); ARC_DISPATCH_RELEASE(_syncQueue); ARC_DEALLOC(super); @@ -281,7 +282,7 @@ static void _ExecuteMainThreadRunLoopSources() { - (void)_endBackgroundTask { GWS_DCHECK([NSThread isMainThread]); if (_backgroundTask != UIBackgroundTaskInvalid) { - if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source) { + if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source4) { [self _stop]; } [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask]; @@ -316,7 +317,7 @@ static void _ExecuteMainThreadRunLoopSources() { _activeConnections -= 1; if (_activeConnections == 0) { dispatch_async(dispatch_get_main_queue(), ^{ - if ((_disconnectDelay > 0.0) && (_source != NULL)) { + if ((_disconnectDelay > 0.0) && (_source4 != NULL)) { if (_disconnectTimer) { CFRunLoopTimerInvalidate(_disconnectTimer); CFRelease(_disconnectTimer); @@ -409,160 +410,197 @@ static inline NSString* _EncodeBase64(NSString* string) { return ARC_AUTORELEASE([[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]); } -- (BOOL)_start:(NSError**)error { - GWS_DCHECK(_source == NULL); - - NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue]; - NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @""); - NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp"); - NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue]; - int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); +- (int)_createListeningSocket:(BOOL)useIPv6 + localAddress:(const void*)address + length:(socklen_t)length + maxPendingConnections:(NSUInteger)maxPendingConnections + error:(NSError**)error { + int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP); if (listeningSocket > 0) { int yes = 1; setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - struct sockaddr_in addr4; - bzero(&addr4, sizeof(addr4)); - addr4.sin_len = sizeof(addr4); - addr4.sin_family = AF_INET; - addr4.sin_port = htons(port); - addr4.sin_addr.s_addr = htonl(INADDR_ANY); - if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) { + if (bind(listeningSocket, address, length) == 0) { if (listen(listeningSocket, (int)maxPendingConnections) == 0) { - GWS_LOG_DEBUG(@"Did open listening socket %i", listeningSocket); - _serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy]; - NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil); - if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) { - _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; - _authenticationBasicAccounts = [[NSMutableDictionary alloc] init]; - NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); - [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { - [_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username]; - }]; - } else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) { - _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; - _authenticationDigestAccounts = [[NSMutableDictionary alloc] init]; - NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); - [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { - [_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username]; - }]; - } - _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]); - _mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue]; - _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue]; - _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue); - dispatch_source_set_cancel_handler(_source, ^{ - - @autoreleasepool { - int result = close(listeningSocket); - if (result != 0) { - GWS_LOG_ERROR(@"Failed closing listening socket: %s (%i)", strerror(errno), errno); - } else { - GWS_LOG_DEBUG(@"Did close listening socket %i", listeningSocket); - } - } - dispatch_semaphore_signal(_sourceSemaphore); - - }); - dispatch_source_set_event_handler(_source, ^{ - - @autoreleasepool { - struct sockaddr remoteSockAddr; - socklen_t remoteAddrLen = sizeof(remoteSockAddr); - int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen); - if (socket > 0) { - NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen]; - - struct sockaddr localSockAddr; - socklen_t localAddrLen = sizeof(localSockAddr); - NSData* localAddress = nil; - if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) { - localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen]; - } else { - GWS_DNOT_REACHED(); - } - - int noSigPipe = 1; - setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE - - GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened -#if __has_feature(objc_arc) - [connection self]; // Prevent compiler from complaining about unused variable / useless statement -#else - [connection release]; -#endif - } else { - GWS_LOG_ERROR(@"Failed accepting socket: %s (%i)", strerror(errno), errno); - } - } - - }); - - if (port == 0) { - struct sockaddr addr; - socklen_t addrlen = sizeof(addr); - if (getsockname(listeningSocket, &addr, &addrlen) == 0) { - struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr; - _port = ntohs(sockaddr->sin_port); - } else { - GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno); - } - } else { - _port = port; - } - - if (bonjourName) { - _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port); - if (_registrationService) { - CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL}; - - CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context); - CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); - CFStreamError streamError = {0}; - CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError); - - _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService); - if (_resolutionService) { - CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context); - CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); - } - } else { - GWS_LOG_ERROR(@"Failed creating CFNetService"); - } - } - - dispatch_resume(_source); - GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL); - if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [_delegate webServerDidStart:self]; - }); - } + GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket); + return listeningSocket; } else { if (error) { *error = GCDWebServerMakePosixError(errno); } - GWS_LOG_ERROR(@"Failed starting listening socket: %s (%i)", strerror(errno), errno); + GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); close(listeningSocket); } } else { if (error) { *error = GCDWebServerMakePosixError(errno); } - GWS_LOG_ERROR(@"Failed binding listening socket: %s (%i)", strerror(errno), errno); + GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); close(listeningSocket); } + } else { if (error) { *error = GCDWebServerMakePosixError(errno); } - GWS_LOG_ERROR(@"Failed creating listening socket: %s (%i)", strerror(errno), errno); + GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); } - return (_source ? YES : NO); + return -1; +} + +- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 { + dispatch_group_enter(_sourceGroup); + dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue); + dispatch_source_set_cancel_handler(source, ^{ + + @autoreleasepool { + int result = close(listeningSocket); + if (result != 0) { + GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + } else { + GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket); + } + } + dispatch_group_leave(_sourceGroup); + + }); + dispatch_source_set_event_handler(source, ^{ + + @autoreleasepool { + struct sockaddr remoteSockAddr; + socklen_t remoteAddrLen = sizeof(remoteSockAddr); + int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen); + if (socket > 0) { + NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen]; + + struct sockaddr localSockAddr; + socklen_t localAddrLen = sizeof(localSockAddr); + NSData* localAddress = nil; + if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) { + localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen]; + GWS_DCHECK((!isIPv6 && localSockAddr.sa_family == AF_INET) || (isIPv6 && localSockAddr.sa_family == AF_INET6)); + } else { + GWS_DNOT_REACHED(); + } + + int noSigPipe = 1; + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE + + GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened +#if __has_feature(objc_arc) + [connection self]; // Prevent compiler from complaining about unused variable / useless statement +#else + [connection release]; +#endif + } else { + GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + } + } + + }); + return source; +} + +- (BOOL)_start:(NSError**)error { + GWS_DCHECK(_source4 == NULL); + + NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue]; + NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue]; + + struct sockaddr_in addr4; + bzero(&addr4, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port); + addr4.sin_addr.s_addr = htonl(INADDR_ANY); + int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error]; + if (listeningSocket4 <= 0) { + return NO; + } + if (port == 0) { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + if (getsockname(listeningSocket4, &addr, &addrlen) == 0) { + struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr; + port = ntohs(sockaddr->sin_port); + } else { + GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno); + } + } + + struct sockaddr_in6 addr6; + bzero(&addr6, sizeof(addr6)); + addr6.sin6_len = sizeof(addr6); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(port); + addr6.sin6_addr = in6addr_any; + int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error]; + if (listeningSocket6 <= 0) { + close(listeningSocket4); + return NO; + } + + _serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy]; + NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil); + if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) { + _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; + _authenticationBasicAccounts = [[NSMutableDictionary alloc] init]; + NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); + [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { + [_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username]; + }]; + } else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) { + _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; + _authenticationDigestAccounts = [[NSMutableDictionary alloc] init]; + NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); + [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { + [_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username]; + }]; + } + _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]); + _mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue]; + _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue]; + + _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO]; + _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES]; + _port = port; + + NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @""); + NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp"); + if (bonjourName) { + _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port); + if (_registrationService) { + CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL}; + + CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context); + CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFStreamError streamError = {0}; + CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError); + + _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService); + if (_resolutionService) { + CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context); + CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + } + } else { + GWS_LOG_ERROR(@"Failed creating CFNetService"); + } + } + + dispatch_resume(_source4); + dispatch_resume(_source6); + GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL); + if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [_delegate webServerDidStart:self]; + }); + } + + return YES; } - (void)_stop { - GWS_DCHECK(_source != NULL); + GWS_DCHECK(_source4 != NULL); if (_registrationService) { if (_resolutionService) { @@ -579,10 +617,13 @@ static inline NSString* _EncodeBase64(NSString* string) { _registrationService = NULL; } - dispatch_source_cancel(_source); - dispatch_semaphore_wait(_sourceSemaphore, DISPATCH_TIME_FOREVER); // Wait until the cancellation handler has been called which guarantees the listening socket is closed - ARC_DISPATCH_RELEASE(_source); - _source = NULL; + dispatch_source_cancel(_source6); + dispatch_source_cancel(_source4); + dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed + ARC_DISPATCH_RELEASE(_source6); + _source6 = NULL; + ARC_DISPATCH_RELEASE(_source4); + _source4 = NULL; _port = 0; ARC_RELEASE(_serverName); @@ -616,7 +657,7 @@ static inline NSString* _EncodeBase64(NSString* string) { - (void)_didEnterBackground:(NSNotification*)notification { GWS_DCHECK([NSThread isMainThread]); GWS_LOG_DEBUG(@"Did enter background"); - if ((_backgroundTask == UIBackgroundTaskInvalid) && _source) { + if ((_backgroundTask == UIBackgroundTaskInvalid) && _source4) { [self _stop]; } } @@ -624,7 +665,7 @@ static inline NSString* _EncodeBase64(NSString* string) { - (void)_willEnterForeground:(NSNotification*)notification { GWS_DCHECK([NSThread isMainThread]); GWS_LOG_DEBUG(@"Will enter foreground"); - if (!_source) { + if (!_source4) { [self _start:NULL]; // TODO: There's probably nothing we can do on failure } } @@ -670,7 +711,7 @@ static inline NSString* _EncodeBase64(NSString* string) { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; } #endif - if (_source) { + if (_source4) { [self _stop]; } ARC_RELEASE(_options); @@ -685,8 +726,8 @@ static inline NSString* _EncodeBase64(NSString* string) { @implementation GCDWebServer (Extensions) - (NSURL*)serverURL { - if (_source) { - NSString* ipAddress = GCDWebServerGetPrimaryIPv4Address(); + if (_source4) { + NSString* ipAddress = GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice if (ipAddress) { if (_port != 80) { return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]]; @@ -699,7 +740,7 @@ static inline NSString* _EncodeBase64(NSString* string) { } - (NSURL*)bonjourServerURL { - if (_source && _resolutionService) { + if (_source4 && _resolutionService) { NSString* name = (ARC_BRIDGE NSString*)CFNetServiceGetTargetHost(_resolutionService); if (name.length) { name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain diff --git a/GCDWebServer/Core/GCDWebServerConnection.h b/GCDWebServer/Core/GCDWebServerConnection.h index 68e6ac9..0df81ff 100644 --- a/GCDWebServer/Core/GCDWebServerConnection.h +++ b/GCDWebServer/Core/GCDWebServerConnection.h @@ -48,6 +48,11 @@ */ @property(nonatomic, readonly) GCDWebServer* server; +/** + * Returns true if the connection is using IPv6. + */ +@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6; + /** * Returns the address of the local peer (i.e. server) of the connection * as a raw "struct sockaddr". @@ -56,7 +61,7 @@ /** * Returns the address of the local peer (i.e. server) of the connection - * as a dotted string. + * as a string. */ @property(nonatomic, readonly) NSString* localAddressString; @@ -68,7 +73,7 @@ /** * Returns the address of the remote peer (i.e. client) of the connection - * as a dotted string. + * as a string. */ @property(nonatomic, readonly) NSString* remoteAddressString; diff --git a/GCDWebServer/Core/GCDWebServerConnection.m b/GCDWebServer/Core/GCDWebServerConnection.m index b06a0b9..263719d 100644 --- a/GCDWebServer/Core/GCDWebServerConnection.m +++ b/GCDWebServer/Core/GCDWebServerConnection.m @@ -365,6 +365,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { } } +- (BOOL)isUsingIPv6 { + const struct sockaddr* localSockAddr = _localAddress.bytes; + return (localSockAddr->sa_family == AF_INET6); +} + - (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode { _statusCode = statusCode; _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1); @@ -622,25 +627,12 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { return self; } -static NSString* _StringFromAddressData(NSData* data) { - NSString* string = nil; - const struct sockaddr* addr = data.bytes; - char hostBuffer[NI_MAXHOST]; - char serviceBuffer[NI_MAXSERV]; - if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) { - string = [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer]; - } else { - GWS_DNOT_REACHED(); - } - return string; -} - - (NSString*)localAddressString { - return _StringFromAddressData(_localAddress); + return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES); } - (NSString*)remoteAddressString { - return _StringFromAddressData(_remoteAddress); + return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES); } - (void)dealloc { diff --git a/GCDWebServer/Core/GCDWebServerFunctions.h b/GCDWebServer/Core/GCDWebServerFunctions.h index 2ef66b2..a8b2857 100644 --- a/GCDWebServer/Core/GCDWebServerFunctions.h +++ b/GCDWebServer/Core/GCDWebServerFunctions.h @@ -57,13 +57,13 @@ NSString* GCDWebServerUnescapeURLString(NSString* string); NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form); /** - * On OS X, returns the IPv4 address as a dotted string of the primary connected - * service or nil if not available. + * On OS X, returns the IPv4 or IPv6 address as a string of the primary + * connected service or nil if not available. * - * On iOS, returns the IPv4 address as a dotted string of the WiFi interface - * if connected or nil otherwise. + * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi + * interface if connected or nil otherwise. */ -NSString* GCDWebServerGetPrimaryIPv4Address(); +NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6); /** * Converts a date into a string using RFC822 formatting. diff --git a/GCDWebServer/Core/GCDWebServerFunctions.m b/GCDWebServer/Core/GCDWebServerFunctions.m index 6c478ec..0a07faf 100644 --- a/GCDWebServer/Core/GCDWebServerFunctions.m +++ b/GCDWebServer/Core/GCDWebServerFunctions.m @@ -223,7 +223,19 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) { return parameters; } -NSString* GCDWebServerGetPrimaryIPv4Address() { +NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) { + NSString* string = nil; + char hostBuffer[NI_MAXHOST]; + char serviceBuffer[NI_MAXSERV]; + if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) { + string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer]; + } else { + GWS_DNOT_REACHED(); + } + return string; +} + +NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) { NSString* address = nil; #if TARGET_OS_IPHONE #if !TARGET_IPHONE_SIMULATOR @@ -233,7 +245,7 @@ NSString* GCDWebServerGetPrimaryIPv4Address() { const char* primaryInterface = NULL; SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL); if (store) { - CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); + CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same if (info) { primaryInterface = [[NSString stringWithString:[(ARC_BRIDGE NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String]; CFRelease(info); @@ -255,11 +267,8 @@ NSString* GCDWebServerGetPrimaryIPv4Address() { { continue; } - if ((ifap->ifa_flags & IFF_UP) && (ifap->ifa_addr->sa_family == AF_INET)) { - char buffer[NI_MAXHOST]; - if (getnameinfo(ifap->ifa_addr, ifap->ifa_addr->sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST | NI_NOFQDN) >= 0) { - address = [NSString stringWithUTF8String:buffer]; - } + if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) { + address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO); break; } } diff --git a/GCDWebServer/Core/GCDWebServerPrivate.h b/GCDWebServer/Core/GCDWebServerPrivate.h index 81e5175..3fa5eea 100644 --- a/GCDWebServer/Core/GCDWebServerPrivate.h +++ b/GCDWebServer/Core/GCDWebServerPrivate.h @@ -26,6 +26,7 @@ */ #import +#import /** * ARC <-> MRC compatibility macros. @@ -212,6 +213,7 @@ extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) extern BOOL GCDWebServerIsTextContentType(NSString* type); extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType); extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2); +extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService); @interface GCDWebServerConnection () - (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket; diff --git a/README.md b/README.md index 97e783d..366c38e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Extra built-in features: * [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files * [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) authentications for password protection * Automatically handle transitions between foreground, background and suspended modes in iOS apps +* Full support for both IPv4 and IPv6 Included extensions: * [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser