修改为东南天坐标系

This commit is contained in:
2026-01-20 09:49:52 +08:00
parent 9538757047
commit 333fad40ac
7201 changed files with 1030888 additions and 85410 deletions

View File

@@ -0,0 +1,46 @@
"""Sans-I/O implementation of SOCKS4, SOCKS4A, and SOCKS5."""
from .exceptions import ProtocolError, SOCKSError
from .socks4 import (
SOCKS4ARequest,
SOCKS4Command,
SOCKS4Connection,
SOCKS4Reply,
SOCKS4ReplyCode,
SOCKS4Request,
)
from .socks5 import (
SOCKS5AType,
SOCKS5AuthMethod,
SOCKS5AuthMethodsRequest,
SOCKS5AuthReply,
SOCKS5Command,
SOCKS5CommandRequest,
SOCKS5Connection,
SOCKS5Reply,
SOCKS5ReplyCode,
SOCKS5UsernamePasswordRequest,
)
__version__ = "1.0.0"
__all__ = [
"SOCKS4Request",
"SOCKS4ARequest",
"SOCKS4Reply",
"SOCKS4Connection",
"SOCKS4Command",
"SOCKS4ReplyCode",
"SOCKS5AType",
"SOCKS5AuthMethodsRequest",
"SOCKS5AuthReply",
"SOCKS5AuthMethod",
"SOCKS5Connection",
"SOCKS5Command",
"SOCKS5CommandRequest",
"SOCKS5ReplyCode",
"SOCKS5Reply",
"SOCKS5UsernamePasswordRequest",
"SOCKSError",
"ProtocolError",
]

View File

@@ -0,0 +1,3 @@
import typing
StrOrBytes = typing.Union[str, bytes]

View File

@@ -0,0 +1,86 @@
"""Backport of @functools.singledispatchmethod to Python <3.7.
Adapted from https://github.com/ikalnytskyi/singledispatchmethod
removing 2.7 specific code.
"""
import functools
import typing
if hasattr(functools, "singledispatchmethod"): # pragma: nocover
singledispatchmethod = functools.singledispatchmethod # type: ignore
else:
update_wrapper = functools.update_wrapper
singledispatch = functools.singledispatch
# The type: ignore below is to avoid mypy erroring due to a
# "already defined" singledispatchmethod, oddly this does not
# happen when using `if sys.version_info >= (3, 8)`
class singledispatchmethod(object): # type: ignore
"""Single-dispatch generic method descriptor.
TODO: Figure out how to type this:
`mypy --strict` returns errors like the following for all decorated methods:
"Untyped decorator makes function "send" untyped."
But this is not a normal function-base decorator, it's a class and it
doesn't have a __call__ method. When decorating the "base" method
__init__ is called, but of course its return type is None.
"""
def __init__(self, func: typing.Callable[..., typing.Any]) -> None:
if not callable(func) and not hasattr(func, "__get__"):
raise TypeError("{!r} is not callable or a descriptor".format(func))
self.dispatcher = singledispatch(func)
self.func = func
def register(
self,
cls: typing.Callable[..., typing.Any],
method: typing.Optional[typing.Callable[..., typing.Any]] = None,
) -> typing.Callable[..., typing.Any]:
"""Register a method on a class for a particular type.
Note in Python <= 3.6 this methods cannot infer the type from the
argument's type annotation, users *must* supply it manually on
decoration, i.e.
@my_method.register(TypeToDispatch)
def _(self, arg: TypeToDispatch) -> None:
...
Versus in Python 3.7+:
@my_method.register
def _(self, arg: TypeToDispatch) -> None:
...
"""
# mypy wants method to be non-optional, but it is required to be
# for decoration to work correctly in our case.
# https://github.com/python/cpython/blob/3.8/Lib/functools.py#L887-L920
# is not type annotated either.
return self.dispatcher.register(cls, func=method) # type: ignore
def __get__(
self, obj: typing.Any, cls: typing.Callable[[typing.Any], typing.Any]
) -> typing.Callable[..., typing.Any]:
def _method(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
method = self.dispatcher.dispatch(args[0].__class__) # type: typing.Any
return method.__get__(obj, cls)(*args, **kwargs)
# The type: ignore below is due to `_method` being given a strict
# "Callable[[VarArg(Any), KwArg(Any)], Any]" which causes a
# 'has no attribute "__isabstractmethod__" error'
# felt safe enough to ignore
_method.__isabstractmethod__ = self.__isabstractmethod__ # type: ignore
_method.register = self.register # type: ignore
update_wrapper(_method, self.func)
return _method
@property
def __isabstractmethod__(self) -> typing.Any:
return getattr(self.func, "__isabstractmethod__", False)

View File

@@ -0,0 +1,6 @@
class SOCKSError(Exception):
"""Generic exception for when something goes wrong"""
class ProtocolError(SOCKSError):
pass

View File

@@ -0,0 +1,253 @@
import enum
import typing
from ._types import StrOrBytes
from .exceptions import ProtocolError, SOCKSError
from .utils import (
AddressType,
decode_address,
encode_address,
get_address_port_tuple_from_address,
)
class SOCKS4ReplyCode(bytes, enum.Enum):
"""Enumeration of SOCKS4 reply codes."""
REQUEST_GRANTED = b"\x5A"
REQUEST_REJECTED_OR_FAILED = b"\x5B"
CONNECTION_FAILED = b"\x5C"
AUTHENTICATION_FAILED = b"\x5D"
class SOCKS4Command(bytes, enum.Enum):
"""Enumeration of SOCKS4 command codes."""
CONNECT = b"\x01"
BIND = b"\x02"
class SOCKS4Request(typing.NamedTuple):
"""Encapsulates a request to the SOCKS4 proxy server
Args:
command: The command to request.
port: The port number to connect to on the target host.
addr: IP address of the target host.
user_id: Optional user ID to be included in the request, if not supplied
the user *must* provide one in the packing operation.
"""
command: SOCKS4Command
port: int
addr: bytes
user_id: typing.Optional[bytes] = None
@classmethod
def from_address(
cls,
command: SOCKS4Command,
address: typing.Union[StrOrBytes, typing.Tuple[StrOrBytes, int]],
user_id: typing.Optional[bytes] = None,
) -> "SOCKS4Request":
"""Convenience class method to build an instance from command and address.
Args:
command: The command to request.
address: A string in the form 'HOST:PORT' or a tuple of ip address string
and port number.
user_id: Optional user ID.
Returns:
A SOCKS4Request instance.
Raises:
SOCKSError: If a domain name or IPv6 address was supplied.
"""
address, port = get_address_port_tuple_from_address(address)
atype, encoded_addr = encode_address(address)
if atype != AddressType.IPV4:
raise SOCKSError(
"IPv6 addresses and domain names are not supported by SOCKS4"
)
return cls(command=command, addr=encoded_addr, port=port, user_id=user_id)
def dumps(self, user_id: typing.Optional[bytes] = None) -> bytes:
"""Packs the instance into a raw binary in the appropriate form.
Args:
user_id: Optional user ID as an override, if not provided the instance's
will be used, if none was provided at initialization an error is raised.
Returns:
The packed request.
Raises:
SOCKSError: If no user was specified in this call or on initialization.
"""
user_id = user_id or self.user_id
if user_id is None:
raise SOCKSError("SOCKS4 requires a user_id, none was specified")
return b"".join(
[
b"\x04",
self.command,
(self.port).to_bytes(2, byteorder="big"),
self.addr,
user_id,
b"\x00",
]
)
class SOCKS4ARequest(typing.NamedTuple):
"""Encapsulates a request to the SOCKS4A proxy server
Args:
command: The command to request.
port: The port number to connect to on the target host.
addr: IP address of the target host.
user_id: Optional user ID to be included in the request, if not supplied
the user *must* provide one in the packing operation.
"""
command: SOCKS4Command
port: int
addr: bytes
user_id: typing.Optional[bytes] = None
@classmethod
def from_address(
cls,
command: SOCKS4Command,
address: typing.Union[StrOrBytes, typing.Tuple[StrOrBytes, int]],
user_id: typing.Optional[bytes] = None,
) -> "SOCKS4ARequest":
"""Convenience class method to build an instance from command and address.
Args:
command: The command to request.
address: A string in the form 'HOST:PORT' or a tuple of ip address string
and port number.
user_id: Optional user ID.
Returns:
A SOCKS4ARequest instance.
"""
address, port = get_address_port_tuple_from_address(address)
atype, encoded_addr = encode_address(address)
return cls(command=command, addr=encoded_addr, port=port, user_id=user_id)
def dumps(self, user_id: typing.Optional[bytes] = None) -> bytes:
"""Packs the instance into a raw binary in the appropriate form.
Args:
user_id: Optional user ID as an override, if not provided the instance's
will be used, if none was provided at initialization an error is raised.
Returns:
The packed request.
Raises:
SOCKSError: If no user was specified in this call or on initialization.
"""
user_id = user_id or self.user_id
if user_id is None:
raise SOCKSError("SOCKS4 requires a user_id, none was specified")
return b"".join(
[
b"\x04",
self.command,
(self.port).to_bytes(2, byteorder="big"),
b"\x00\x00\x00\xFF", # arbitrary final non-zero byte
user_id,
b"\x00",
self.addr,
b"\x00",
]
)
class SOCKS4Reply(typing.NamedTuple):
"""Encapsulates a reply from the SOCKS4 proxy server
Args:
reply_code: The code representing the type of reply.
port: The port number returned.
addr: Optional IP address returned.
"""
reply_code: SOCKS4ReplyCode
port: int
addr: typing.Optional[str]
@classmethod
def loads(cls, data: bytes) -> "SOCKS4Reply":
"""Unpacks the reply data into an instance.
Returns:
The unpacked reply instance.
Raises:
ProtocolError: If the data does not match the spec.
"""
if len(data) != 8 or data[0:1] != b"\x00":
raise ProtocolError("Malformed reply")
try:
return cls(
reply_code=SOCKS4ReplyCode(data[1:2]),
port=int.from_bytes(data[2:4], byteorder="big"),
addr=decode_address(AddressType.IPV4, data[4:8]),
)
except ValueError as exc:
raise ProtocolError("Malformed reply") from exc
class SOCKS4Connection:
"""Encapsulates a SOCKS4 and SOCKS4A connection.
Packs request objects into data suitable to be send and unpacks reply
data into their appropriate reply objects.
Args:
user_id: The user ID to be sent as part of the requests.
"""
def __init__(self, user_id: bytes):
self.user_id = user_id
self._data_to_send = bytearray()
self._received_data = bytearray()
def send(self, request: typing.Union[SOCKS4Request, SOCKS4ARequest]) -> None:
"""Packs a request object and adds it to the send data buffer.
Args:
request: The request instance to be packed.
"""
user_id = request.user_id or self.user_id
self._data_to_send += request.dumps(user_id=user_id)
def receive_data(self, data: bytes) -> SOCKS4Reply:
"""Unpacks response data into a reply object.
Args:
data: The raw response data from the proxy server.
Returns:
The appropriate reply object.
"""
self._received_data += data
return SOCKS4Reply.loads(bytes(self._received_data))
def data_to_send(self) -> bytes:
"""Returns the data to be sent via the I/O library of choice.
Also clears the connection's buffer.
"""
data = bytes(self._data_to_send)
self._data_to_send = bytearray()
return data

View File

@@ -0,0 +1,399 @@
import enum
import typing
from ._types import StrOrBytes
from .compat import singledispatchmethod
from .exceptions import ProtocolError
from .utils import (
AddressType,
decode_address,
encode_address,
get_address_port_tuple_from_address,
)
class SOCKS5AuthMethod(bytes, enum.Enum):
"""Enumeration of SOCKS5 authentication methods."""
NO_AUTH_REQUIRED = b"\x00"
GSSAPI = b"\x01"
USERNAME_PASSWORD = b"\x02"
NO_ACCEPTABLE_METHODS = b"\xFF"
class SOCKS5Command(bytes, enum.Enum):
"""Enumeration of SOCKS5 commands."""
CONNECT = b"\x01"
BIND = b"\x02"
UDP_ASSOCIATE = b"\x03"
class SOCKS5AType(bytes, enum.Enum):
"""Enumeration of SOCKS5 address types."""
IPV4_ADDRESS = b"\x01"
DOMAIN_NAME = b"\x03"
IPV6_ADDRESS = b"\x04"
@classmethod
def from_atype(cls, atype: AddressType) -> "SOCKS5AType":
if atype == AddressType.IPV4:
return SOCKS5AType.IPV4_ADDRESS
elif atype == AddressType.DN:
return SOCKS5AType.DOMAIN_NAME
elif atype == AddressType.IPV6:
return SOCKS5AType.IPV6_ADDRESS
raise ValueError(atype)
class SOCKS5ReplyCode(bytes, enum.Enum):
"""Enumeration of SOCKS5 reply codes."""
SUCCEEDED = b"\x00"
GENERAL_SERVER_FAILURE = b"\x01"
CONNECTION_NOT_ALLOWED_BY_RULESET = b"\x02"
NETWORK_UNREACHABLE = b"\x03"
HOST_UNREACHABLE = b"\x04"
CONNECTION_REFUSED = b"\x05"
TTL_EXPIRED = b"\x06"
COMMAND_NOT_SUPPORTED = b"\x07"
ADDRESS_TYPE_NOT_SUPPORTED = b"\x08"
class SOCKS5AuthMethodsRequest(typing.NamedTuple):
"""Encapsulates a request to the proxy for available authentication methods.
Args:
methods: A list of acceptable authentication methods.
"""
methods: typing.List[SOCKS5AuthMethod]
def dumps(self) -> bytes:
"""Packs the instance into a raw binary in the appropriate form."""
return b"".join(
[
b"\x05",
len(self.methods).to_bytes(1, byteorder="big"),
b"".join(self.methods),
]
)
class SOCKS5AuthReply(typing.NamedTuple):
"""Encapsulates a reply from the proxy with the authentication method to be used.
Args:
method: The authentication method to be used.
Raises:
ProtocolError: If the data does not conform with the expected structure.
"""
method: SOCKS5AuthMethod
@classmethod
def loads(cls, data: bytes) -> "SOCKS5AuthReply":
"""Unpacks the authentication reply data into an instance.
Returns:
The unpacked authentication reply instance.
Raises:
ProtocolError: If the data does not match the spec.
"""
if len(data) != 2:
raise ProtocolError("Malformed reply")
try:
return cls(method=SOCKS5AuthMethod(data[1:2]))
except ValueError as exc:
raise ProtocolError("Malformed reply") from exc
class SOCKS5UsernamePasswordRequest(typing.NamedTuple):
"""Encapsulates a username/password authentication request to the proxy server."""
username: bytes
password: bytes
def dumps(self) -> bytes:
"""Packs the instance into a raw binary in the appropriate form.
Returns:
The packed request.
"""
return b"".join(
[
b"\x01",
len(self.username).to_bytes(1, byteorder="big"),
self.username,
len(self.password).to_bytes(1, byteorder="big"),
self.password,
]
)
class SOCKS5UsernamePasswordReply(typing.NamedTuple):
"""Encapsulates a username/password authentication reply from the proxy server."""
success: bool
@classmethod
def loads(cls, data: bytes) -> "SOCKS5UsernamePasswordReply":
"""Unpacks the reply authentication data into an instance.
Returns:
The unpacked authentication reply instance.
"""
return cls(success=data == b"\x01\x00")
class SOCKS5CommandRequest(typing.NamedTuple):
"""Encapsulates a command request to the proxy server.
Args:
command: The command to request.
atype: The address type of the addr field.
addr: Address of the target host.
port: The port number to connect to on the target host.
"""
command: SOCKS5Command
atype: SOCKS5AType
addr: bytes
port: int
@classmethod
def from_address(
cls,
command: SOCKS5Command,
address: typing.Union[StrOrBytes, typing.Tuple[StrOrBytes, int]],
) -> "SOCKS5CommandRequest":
"""Convenience class method to build an instance from command and address.
Args:
command: The command to request.
address: A string in the form 'HOST:PORT' or a tuple of ip address string
and port number. The address type will be inferred.
Returns:
A SOCKS5CommandRequest instance.
Raises:
SOCKSError: If a domain name or IPv6 address was supplied.
"""
address, port = get_address_port_tuple_from_address(address)
atype, encoded_addr = encode_address(address)
return cls(
command=command,
atype=SOCKS5AType.from_atype(atype),
addr=encoded_addr,
port=port,
)
def dumps(self) -> bytes:
"""Packs the instance into a raw binary in the appropriate form.
Returns:
The packed request.
"""
return b"".join(
[
b"\x05",
self.command,
b"\x00",
self.atype,
self.packed_addr,
(self.port).to_bytes(2, byteorder="big"),
]
)
@property
def packed_addr(self) -> bytes:
"""Property returning the packed address in the correct form for its type."""
if self.atype == SOCKS5AType.IPV4_ADDRESS:
assert len(self.addr) == 4
return self.addr
elif self.atype == SOCKS5AType.IPV6_ADDRESS:
assert len(self.addr) == 16
return self.addr
else:
length = len(self.addr)
return length.to_bytes(1, byteorder="big") + self.addr
class SOCKS5Reply(typing.NamedTuple):
"""Encapsulates a reply from the SOCKS5 proxy server
Args:
reply_code: The code representing the type of reply.
atype: The address type of the addr field.
addr: Optional IP address returned.
port: The port number returned.
"""
reply_code: SOCKS5ReplyCode
atype: SOCKS5AType
addr: str
port: int
@classmethod
def loads(cls, data: bytes) -> "SOCKS5Reply":
"""Unpacks the reply data into an instance.
Returns:
The unpacked reply instance.
Raises:
ProtocolError: If the data does not match the spec.
"""
if data[0:1] != b"\x05":
raise ProtocolError("Malformed reply")
try:
atype = SOCKS5AType(data[3:4])
return cls(
reply_code=SOCKS5ReplyCode(data[1:2]),
atype=atype,
addr=decode_address(AddressType.from_socks5_atype(atype), data[4:-2]),
port=int.from_bytes(data[-2:], byteorder="big"),
)
except ValueError as exc:
raise ProtocolError("Malformed reply") from exc
class SOCKS5Datagram(typing.NamedTuple):
"""Encapsulates a SOCKS5 datagram for UDP connections.
Currently not implemented.
"""
atype: SOCKS5AType
addr: bytes
port: int
data: bytes
fragment: int
last_fragment: bool
@classmethod
def loads(cls, data: bytes) -> "SOCKS5Datagram":
raise NotImplementedError() # pragma: nocover
def dumps(self) -> bytes:
raise NotImplementedError() # pragma: nocover
class SOCKS5State(enum.IntEnum):
"""Enumeration of SOCKS5 protocol states."""
CLIENT_AUTH_REQUIRED = 1
SERVER_AUTH_REPLY = 2
CLIENT_AUTHENTICATED = 3
TUNNEL_READY = 4
CLIENT_WAITING_FOR_USERNAME_PASSWORD = 5
SERVER_VERIFY_USERNAME_PASSWORD = 6
MUST_CLOSE = 7
SOCKS5RequestType = typing.Union[SOCKS5AuthMethodsRequest, SOCKS5CommandRequest]
class SOCKS5Connection:
"""Encapsulates a SOCKS5 connection.
Packs request objects into data suitable to be send and unpacks reply
data into their appropriate reply objects.
"""
def __init__(self) -> None:
self._data_to_send = bytearray()
self._received_data = bytearray()
self._state = SOCKS5State.CLIENT_AUTH_REQUIRED
@property
def state(self) -> SOCKS5State:
"""Returns the current state of the protocol."""
return self._state
@singledispatchmethod # type: ignore
def send(self, request: SOCKS5RequestType) -> None:
"""Packs a request object and adds it to the send data buffer.
Also progresses the protocol state of the connection.
Args:
request: The request instance to be packed.
"""
raise NotImplementedError() # pragma: nocover
@send.register(SOCKS5AuthMethodsRequest) # type: ignore
def _auth_methods(self, request: SOCKS5AuthMethodsRequest) -> None:
self._data_to_send += request.dumps()
self._state = SOCKS5State.SERVER_AUTH_REPLY
@send.register(SOCKS5UsernamePasswordRequest) # type: ignore
def _auth_username_password(self, request: SOCKS5UsernamePasswordRequest) -> None:
if self._state != SOCKS5State.CLIENT_WAITING_FOR_USERNAME_PASSWORD:
raise ProtocolError("Not currently waiting for username and password")
self._state = SOCKS5State.SERVER_VERIFY_USERNAME_PASSWORD
self._data_to_send += request.dumps()
@send.register(SOCKS5CommandRequest) # type: ignore
def _command(self, request: SOCKS5AuthMethodsRequest) -> None:
if self._state < SOCKS5State.CLIENT_AUTHENTICATED:
raise ProtocolError(
"SOCKS5 connections must be authenticated before sending a request"
)
self._data_to_send += request.dumps()
def receive_data(
self, data: bytes
) -> typing.Union[SOCKS5AuthReply, SOCKS5Reply, SOCKS5UsernamePasswordReply]:
"""Unpacks response data into a reply object.
Args:
data: The raw response data from the proxy server.
Returns:
A reply instance corresponding to the connection state and reply data.
"""
if self._state == SOCKS5State.SERVER_AUTH_REPLY:
auth_reply = SOCKS5AuthReply.loads(data)
if auth_reply.method == SOCKS5AuthMethod.USERNAME_PASSWORD:
self._state = SOCKS5State.CLIENT_WAITING_FOR_USERNAME_PASSWORD
elif auth_reply.method == SOCKS5AuthMethod.NO_AUTH_REQUIRED:
self._state = SOCKS5State.CLIENT_AUTHENTICATED
return auth_reply
if self._state == SOCKS5State.SERVER_VERIFY_USERNAME_PASSWORD:
username_password_reply = SOCKS5UsernamePasswordReply.loads(data)
if username_password_reply.success:
self._state = SOCKS5State.CLIENT_AUTHENTICATED
else:
self._state = SOCKS5State.MUST_CLOSE
return username_password_reply
if self._state == SOCKS5State.CLIENT_AUTHENTICATED:
reply = SOCKS5Reply.loads(data)
if reply.reply_code == SOCKS5ReplyCode.SUCCEEDED:
self._state = SOCKS5State.TUNNEL_READY
else:
self._state = SOCKS5State.MUST_CLOSE
return reply
raise NotImplementedError() # pragma: nocover
def data_to_send(self) -> bytes:
"""Returns the data to be sent via the I/O library of choice.
Also clears the connection's buffer.
"""
data = bytes(self._data_to_send)
self._data_to_send = bytearray()
return data

View File

@@ -0,0 +1,95 @@
import enum
import functools
import re
import socket
import typing
from ._types import StrOrBytes
if typing.TYPE_CHECKING:
from socksio.socks5 import SOCKS5AType # pragma: nocover
IP_V6_WITH_PORT_REGEX = re.compile(r"^\[(?P<address>[^\]]+)\]:(?P<port>\d+)$")
class AddressType(enum.Enum):
IPV4 = "IPV4"
IPV6 = "IPV6"
DN = "DN"
@classmethod
def from_socks5_atype(cls, socks5atype: "SOCKS5AType") -> "AddressType":
from socksio.socks5 import SOCKS5AType
if socks5atype == SOCKS5AType.IPV4_ADDRESS:
return AddressType.IPV4
elif socks5atype == SOCKS5AType.DOMAIN_NAME:
return AddressType.DN
elif socks5atype == SOCKS5AType.IPV6_ADDRESS:
return AddressType.IPV6
raise ValueError(socks5atype)
@functools.lru_cache(maxsize=64)
def encode_address(addr: StrOrBytes) -> typing.Tuple[AddressType, bytes]:
"""Determines the type of address and encodes it into the format SOCKS expects"""
addr = addr.decode() if isinstance(addr, bytes) else addr
try:
return AddressType.IPV6, socket.inet_pton(socket.AF_INET6, addr)
except OSError:
try:
return AddressType.IPV4, socket.inet_pton(socket.AF_INET, addr)
except OSError:
return AddressType.DN, addr.encode()
@functools.lru_cache(maxsize=64)
def decode_address(address_type: AddressType, encoded_addr: bytes) -> str:
"""Decodes the address from a SOCKS reply"""
if address_type == AddressType.IPV6:
return socket.inet_ntop(socket.AF_INET6, encoded_addr)
elif address_type == AddressType.IPV4:
return socket.inet_ntop(socket.AF_INET, encoded_addr)
else:
assert address_type == AddressType.DN
return encoded_addr.decode()
def split_address_port_from_string(address: StrOrBytes) -> typing.Tuple[str, int]:
"""Returns a tuple (address: str, port: int) from an address string with a port
i.e. '127.0.0.1:8080', '[0:0:0:0:0:0:0:1]:3080' or 'localhost:8080'.
Note no validation is done on the domain or IP itself.
"""
address = address.decode() if isinstance(address, bytes) else address
match = re.match(IP_V6_WITH_PORT_REGEX, address)
if match:
address, str_port = match.group("address"), match.group("port")
else:
address, _, str_port = address.partition(":")
try:
return address, int(str_port)
except ValueError:
raise ValueError(
"Invalid address + port. Please supply a valid domain name, IPV4 or IPV6 "
"address with the port as a suffix, i.e. `127.0.0.1:3080`, "
"`[0:0:0:0:0:0:0:1]:3080` or `localhost:3080`"
) from None
def get_address_port_tuple_from_address(
address: typing.Union[StrOrBytes, typing.Tuple[StrOrBytes, int]]
) -> typing.Tuple[str, int]:
"""Returns an (address, port) from an address string-like or tuple."""
if isinstance(address, tuple):
address, port = address
if isinstance(address, bytes):
address = address.decode()
if isinstance(port, (str, bytes)):
port = int(port)
else:
address, port = split_address_port_from_string(address)
return address, port