Add support for IPv6 hostnames
This commit is contained in:
		@@ -47,6 +47,30 @@ if os.name == "posix":
 | 
				
			|||||||
else:
 | 
					else:
 | 
				
			||||||
    ParallelizationMixIn = socketserver.ThreadingMixIn
 | 
					    ParallelizationMixIn = socketserver.ThreadingMixIn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HAS_IPV6 = socket.has_ipv6
 | 
				
			||||||
 | 
					if hasattr(socket, "EAI_NONAME"):
 | 
				
			||||||
 | 
					    EAI_NONAME = socket.EAI_NONAME
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    HAS_IPV6 = False
 | 
				
			||||||
 | 
					if hasattr(socket, "EAI_ADDRFAMILY"):
 | 
				
			||||||
 | 
					    EAI_ADDRFAMILY = socket.EAI_ADDRFAMILY
 | 
				
			||||||
 | 
					elif os.name == "nt":
 | 
				
			||||||
 | 
					    EAI_ADDRFAMILY = None
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    HAS_IPV6 = False
 | 
				
			||||||
 | 
					if hasattr(socket, "IPPROTO_IPV6"):
 | 
				
			||||||
 | 
					    IPPROTO_IPV6 = socket.IPPROTO_IPV6
 | 
				
			||||||
 | 
					elif os.name == "nt":
 | 
				
			||||||
 | 
					    IPPROTO_IPV6 = 41
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    HAS_IPV6 = False
 | 
				
			||||||
 | 
					if hasattr(socket, "IPV6_V6ONLY"):
 | 
				
			||||||
 | 
					    IPV6_V6ONLY = socket.IPV6_V6ONLY
 | 
				
			||||||
 | 
					elif os.name == "nt":
 | 
				
			||||||
 | 
					    IPV6_V6ONLY = 27
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    HAS_IPV6 = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ParallelHTTPServer(ParallelizationMixIn,
 | 
					class ParallelHTTPServer(ParallelizationMixIn,
 | 
				
			||||||
                         wsgiref.simple_server.WSGIServer):
 | 
					                         wsgiref.simple_server.WSGIServer):
 | 
				
			||||||
@@ -74,13 +98,17 @@ class ParallelHTTPServer(ParallelizationMixIn,
 | 
				
			|||||||
            self.server_port = port
 | 
					            self.server_port = port
 | 
				
			||||||
            self.setup_environ()
 | 
					            self.setup_environ()
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        ipv6 = ":" in self.server_address[0]
 | 
					        try:
 | 
				
			||||||
        if ipv6 and self.address_family == socket.AF_INET:
 | 
					            super().server_bind()
 | 
				
			||||||
 | 
					        except socket.gaierror as e:
 | 
				
			||||||
 | 
					            if (not HAS_IPV6 or self.address_family != socket.AF_INET or
 | 
				
			||||||
 | 
					                    e.errno not in (EAI_NONAME, EAI_ADDRFAMILY)):
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					            # Try again with IPv6
 | 
				
			||||||
            self.address_family = socket.AF_INET6
 | 
					            self.address_family = socket.AF_INET6
 | 
				
			||||||
            self.socket = socket.socket(self.address_family, self.socket_type)
 | 
					            self.socket = socket.socket(self.address_family, self.socket_type)
 | 
				
			||||||
        if ipv6:
 | 
					 | 
				
			||||||
            # Only allow IPv6 connections to the IPv6 socket
 | 
					            # Only allow IPv6 connections to the IPv6 socket
 | 
				
			||||||
            self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
 | 
					            self.socket.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1)
 | 
				
			||||||
            super().server_bind()
 | 
					            super().server_bind()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_request(self):
 | 
					    def get_request(self):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,12 +19,14 @@ Test the internal server.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
import socket
 | 
					import socket
 | 
				
			||||||
import ssl
 | 
					import ssl
 | 
				
			||||||
import tempfile
 | 
					import tempfile
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					import warnings
 | 
				
			||||||
from urllib import request
 | 
					from urllib import request
 | 
				
			||||||
from urllib.error import HTTPError, URLError
 | 
					from urllib.error import HTTPError, URLError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,6 +34,8 @@ from radicale import config, server
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from .helpers import get_file_path
 | 
					from .helpers import get_file_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pytest  # isort:skip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DisabledRedirectHandler(request.HTTPRedirectHandler):
 | 
					class DisabledRedirectHandler(request.HTTPRedirectHandler):
 | 
				
			||||||
    def http_error_302(self, req, fp, code, msg, headers):
 | 
					    def http_error_302(self, req, fp, code, msg, headers):
 | 
				
			||||||
@@ -66,7 +70,10 @@ class TestBaseServerRequests:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def teardown(self):
 | 
					    def teardown(self):
 | 
				
			||||||
        self.shutdown_socket.sendall(b" ")
 | 
					        self.shutdown_socket.sendall(b" ")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
            self.thread.join()
 | 
					            self.thread.join()
 | 
				
			||||||
 | 
					        except RuntimeError:  # Thread never started
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
        shutil.rmtree(self.colpath)
 | 
					        shutil.rmtree(self.colpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def request(self, method, path, data=None, **headers):
 | 
					    def request(self, method, path, data=None, **headers):
 | 
				
			||||||
@@ -100,3 +107,19 @@ class TestBaseServerRequests:
 | 
				
			|||||||
        self.thread.start()
 | 
					        self.thread.start()
 | 
				
			||||||
        status, _, _ = self.request("GET", "/")
 | 
					        status, _, _ = self.request("GET", "/")
 | 
				
			||||||
        assert status == 302
 | 
					        assert status == 302
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_ipv6(self):
 | 
				
			||||||
 | 
					        if not server.HAS_IPV6:
 | 
				
			||||||
 | 
					            pytest.skip("IPv6 not support")
 | 
				
			||||||
 | 
					        if os.name == "nt" and os.environ.get("WINE_PYTHON"):
 | 
				
			||||||
 | 
					            warnings.warn("WORKAROUND: incomplete errno conversion in WINE")
 | 
				
			||||||
 | 
					            server.EAI_ADDRFAMILY = -9
 | 
				
			||||||
 | 
					        with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as sock:
 | 
				
			||||||
 | 
					            sock.setsockopt(server.IPPROTO_IPV6, server.IPV6_V6ONLY, 1)
 | 
				
			||||||
 | 
					            # Find available port
 | 
				
			||||||
 | 
					            sock.bind(("localhost", 0))
 | 
				
			||||||
 | 
					            self.sockname = sock.getsockname()[:2]
 | 
				
			||||||
 | 
					            self.configuration["server"]["hosts"] = "[%s]:%d" % self.sockname
 | 
				
			||||||
 | 
					        self.thread.start()
 | 
				
			||||||
 | 
					        status, _, _ = self.request("GET", "/")
 | 
				
			||||||
 | 
					        assert status == 302
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user