Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/urllib3/util/connection.py: 36%

66 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:05 +0000

1from __future__ import annotations 

2 

3import socket 

4import typing 

5 

6from ..exceptions import LocationParseError 

7from .timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT 

8 

9_TYPE_SOCKET_OPTIONS = typing.Sequence[typing.Tuple[int, int, typing.Union[int, bytes]]] 

10 

11if typing.TYPE_CHECKING: 

12 from .._base_connection import BaseHTTPConnection 

13 

14 

15def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specific 

16 """ 

17 Returns True if the connection is dropped and should be closed. 

18 :param conn: :class:`urllib3.connection.HTTPConnection` object. 

19 """ 

20 return not conn.is_connected 

21 

22 

23# This function is copied from socket.py in the Python 2.7 standard 

24# library test suite. Added to its signature is only `socket_options`. 

25# One additional modification is that we avoid binding to IPv6 servers 

26# discovered in DNS if the system doesn't have IPv6 functionality. 

27def create_connection( 

28 address: tuple[str, int], 

29 timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, 

30 source_address: tuple[str, int] | None = None, 

31 socket_options: _TYPE_SOCKET_OPTIONS | None = None, 

32) -> socket.socket: 

33 """Connect to *address* and return the socket object. 

34 

35 Convenience function. Connect to *address* (a 2-tuple ``(host, 

36 port)``) and return the socket object. Passing the optional 

37 *timeout* parameter will set the timeout on the socket instance 

38 before attempting to connect. If no *timeout* is supplied, the 

39 global default timeout setting returned by :func:`socket.getdefaulttimeout` 

40 is used. If *source_address* is set it must be a tuple of (host, port) 

41 for the socket to bind as a source address before making the connection. 

42 An host of '' or port 0 tells the OS to use the default. 

43 """ 

44 

45 host, port = address 

46 if host.startswith("["): 

47 host = host.strip("[]") 

48 err = None 

49 

50 # Using the value from allowed_gai_family() in the context of getaddrinfo lets 

51 # us select whether to work with IPv4 DNS records, IPv6 records, or both. 

52 # The original create_connection function always returns all records. 

53 family = allowed_gai_family() 

54 

55 try: 

56 host.encode("idna") 

57 except UnicodeError: 

58 raise LocationParseError(f"'{host}', label empty or too long") from None 

59 

60 for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): 

61 af, socktype, proto, canonname, sa = res 

62 sock = None 

63 try: 

64 sock = socket.socket(af, socktype, proto) 

65 

66 # If provided, set socket level options before connecting. 

67 _set_socket_options(sock, socket_options) 

68 

69 if timeout is not _DEFAULT_TIMEOUT: 

70 sock.settimeout(timeout) 

71 if source_address: 

72 sock.bind(source_address) 

73 sock.connect(sa) 

74 # Break explicitly a reference cycle 

75 err = None 

76 return sock 

77 

78 except OSError as _: 

79 err = _ 

80 if sock is not None: 

81 sock.close() 

82 

83 if err is not None: 

84 try: 

85 raise err 

86 finally: 

87 # Break explicitly a reference cycle 

88 err = None 

89 else: 

90 raise OSError("getaddrinfo returns an empty list") 

91 

92 

93def _set_socket_options( 

94 sock: socket.socket, options: _TYPE_SOCKET_OPTIONS | None 

95) -> None: 

96 if options is None: 

97 return 

98 

99 for opt in options: 

100 sock.setsockopt(*opt) 

101 

102 

103def allowed_gai_family() -> socket.AddressFamily: 

104 """This function is designed to work in the context of 

105 getaddrinfo, where family=socket.AF_UNSPEC is the default and 

106 will perform a DNS search for both IPv6 and IPv4 records.""" 

107 

108 family = socket.AF_INET 

109 if HAS_IPV6: 

110 family = socket.AF_UNSPEC 

111 return family 

112 

113 

114def _has_ipv6(host: str) -> bool: 

115 """Returns True if the system can bind an IPv6 address.""" 

116 sock = None 

117 has_ipv6 = False 

118 

119 if socket.has_ipv6: 

120 # has_ipv6 returns true if cPython was compiled with IPv6 support. 

121 # It does not tell us if the system has IPv6 support enabled. To 

122 # determine that we must bind to an IPv6 address. 

123 # https://github.com/urllib3/urllib3/pull/611 

124 # https://bugs.python.org/issue658327 

125 try: 

126 sock = socket.socket(socket.AF_INET6) 

127 sock.bind((host, 0)) 

128 has_ipv6 = True 

129 except Exception: 

130 pass 

131 

132 if sock: 

133 sock.close() 

134 return has_ipv6 

135 

136 

137HAS_IPV6 = _has_ipv6("::1")