Pierre-Olivier Latour 11 жил өмнө
parent
commit
75d018a375

+ 186 - 145
GCDWebServer/Core/GCDWebServer.m

@@ -155,7 +155,7 @@ static void _ExecuteMainThreadRunLoopSources() {
 @private
   id<GCDWebServerDelegate> __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 -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");
+    }
   }
-  return (_source ? YES : NO);
+  
+  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

+ 7 - 2
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;
 

+ 7 - 15
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 {

+ 5 - 5
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.

+ 16 - 7
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;
       }
     }

+ 2 - 0
GCDWebServer/Core/GCDWebServerPrivate.h

@@ -26,6 +26,7 @@
  */
 
 #import <os/object.h>
+#import <sys/socket.h>
 
 /**
  *  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;

+ 1 - 0
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