| 1 | # SPDX-License-Identifier: MIT |
| 2 | from __future__ import absolute_import |
| 3 | import socket |
| 4 | from .wait import wait_for_read |
| 5 | from .selectors import HAS_SELECT, SelectorError |
| 6 | |
| 7 | |
| 8 | def is_connection_dropped(conn): # Platform-specific |
| 9 | """ |
| 10 | Returns True if the connection is dropped and should be closed. |
| 11 | |
| 12 | :param conn: |
| 13 | :class:`httplib.HTTPConnection` object. |
| 14 | |
| 15 | Note: For platforms like AppEngine, this will always return ``False`` to |
| 16 | let the platform handle connection recycling transparently for us. |
| 17 | """ |
| 18 | sock = getattr(conn, 'sock', False) |
| 19 | if sock is False: # Platform-specific: AppEngine |
| 20 | return False |
| 21 | if sock is None: # Connection already closed (such as by httplib). |
| 22 | return True |
| 23 | |
| 24 | if not HAS_SELECT: |
| 25 | return False |
| 26 | |
| 27 | try: |
| 28 | return bool(wait_for_read(sock, timeout=0.0)) |
| 29 | except SelectorError: |
| 30 | return True |
| 31 | |
| 32 | |
| 33 | # This function is copied from socket.py in the Python 2.7 standard |
| 34 | # library test suite. Added to its signature is only `socket_options`. |
| 35 | # One additional modification is that we avoid binding to IPv6 servers |
| 36 | # discovered in DNS if the system doesn't have IPv6 functionality. |
| 37 | def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, |
| 38 | source_address=None, socket_options=None): |
| 39 | """Connect to *address* and return the socket object. |
| 40 | |
| 41 | Convenience function. Connect to *address* (a 2-tuple ``(host, |
| 42 | port)``) and return the socket object. Passing the optional |
| 43 | *timeout* parameter will set the timeout on the socket instance |
| 44 | before attempting to connect. If no *timeout* is supplied, the |
| 45 | global default timeout setting returned by :func:`getdefaulttimeout` |
| 46 | is used. If *source_address* is set it must be a tuple of (host, port) |
| 47 | for the socket to bind as a source address before making the connection. |
| 48 | An host of '' or port 0 tells the OS to use the default. |
| 49 | """ |
| 50 | |
| 51 | host, port = address |
| 52 | if host.startswith('['): |
| 53 | host = host.strip('[]') |
| 54 | err = None |
| 55 | |
| 56 | # Using the value from allowed_gai_family() in the context of getaddrinfo lets |
| 57 | # us select whether to work with IPv4 DNS records, IPv6 records, or both. |
| 58 | # The original create_connection function always returns all records. |
| 59 | family = allowed_gai_family() |
| 60 | |
| 61 | for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): |
| 62 | af, socktype, proto, canonname, sa = res |
| 63 | sock = None |
| 64 | try: |
| 65 | sock = socket.socket(af, socktype, proto) |
| 66 | |
| 67 | # If provided, set socket level options before connecting. |
| 68 | _set_socket_options(sock, socket_options) |
| 69 | |
| 70 | if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: |
| 71 | sock.settimeout(timeout) |
| 72 | if source_address: |
| 73 | sock.bind(source_address) |
| 74 | sock.connect(sa) |
| 75 | return sock |
| 76 | |
| 77 | except socket.error as e: |
| 78 | err = e |
| 79 | if sock is not None: |
| 80 | sock.close() |
| 81 | sock = None |
| 82 | |
| 83 | if err is not None: |
| 84 | raise err |
| 85 | |
| 86 | raise socket.error("getaddrinfo returns an empty list") |
| 87 | |
| 88 | |
| 89 | def _set_socket_options(sock, options): |
| 90 | if options is None: |
| 91 | return |
| 92 | |
| 93 | for opt in options: |
| 94 | sock.setsockopt(*opt) |
| 95 | |
| 96 | |
| 97 | def allowed_gai_family(): |
| 98 | """This function is designed to work in the context of |
| 99 | getaddrinfo, where family=socket.AF_UNSPEC is the default and |
| 100 | will perform a DNS search for both IPv6 and IPv4 records.""" |
| 101 | |
| 102 | family = socket.AF_INET |
| 103 | if HAS_IPV6: |
| 104 | family = socket.AF_UNSPEC |
| 105 | return family |
| 106 | |
| 107 | |
| 108 | def _has_ipv6(host): |
| 109 | """ Returns True if the system can bind an IPv6 address. """ |
| 110 | sock = None |
| 111 | has_ipv6 = False |
| 112 | |
| 113 | if socket.has_ipv6: |
| 114 | # has_ipv6 returns true if cPython was compiled with IPv6 support. |
| 115 | # It does not tell us if the system has IPv6 support enabled. To |
| 116 | # determine that we must bind to an IPv6 address. |
| 117 | # https://github.com/shazow/urllib3/pull/611 |
| 118 | # https://bugs.python.org/issue658327 |
| 119 | try: |
| 120 | sock = socket.socket(socket.AF_INET6) |
| 121 | sock.bind((host, 0)) |
| 122 | has_ipv6 = True |
| 123 | except Exception: |
| 124 | pass |
| 125 | |
| 126 | if sock: |
| 127 | sock.close() |
| 128 | return has_ipv6 |
| 129 | |
| 130 | |
| 131 | HAS_IPV6 = _has_ipv6('::1') |