CVE-2021-21240 (Python, published 2021-02-08T20:15Z)
Description: value: httplib2 is a comprehensive HTTP client library for Python. In httplib2 before version 0.19.0, a malicious server which responds with long series of "\xa0" characters in the "www-authenticate" header may cause Denial of Service (CPU burn while parsing header) of the httplib2 client accessing said server. This is fixed in version 0.19.0 which contains a new implementation of auth headers parsing using the pyparsing library.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-400 \u2014 Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Repository: https://github.com/httplib2/httplib2
Files changed:
python2/httplib2/__init__.py
python2/httplib2/auth.py
python2/httplib2/error.py
python3/httplib2/__init__.py
python3/httplib2/auth.py
python3/httplib2/error.py
requirements.txt
script/release
script/test
setup.py
tests/__init__.py
tests/test_auth.py
Commit message: parse auth headers using pyparsing instead of regexp

Fixes CPU burn DoS by cubic complexity of whitespace matching in
WWW_AUTH_RELAXED (default) regexp.


Vulnerable code:
def _ssl_wrap_socket(
    sock, key_file, cert_file, disable_validation, ca_certs, ssl_version, hostname, key_password
):
            sock,
            keyfile=key_file,
            certfile=cert_file,
            cert_reqs=cert_reqs,
            ca_certs=ca_certs,
            ssl_version=ssl_version,
    return dict(
        [
            (key.lower(), NORMALIZE_SPACE.sub(value, " ").strip())
            for (key, value) in headers.iteritems()
        ]
    )
            tuple([x.strip().lower() for x in part.split("=", 1)])
            for part in parts
            if -1 != part.find("=")
        ]
        parts_wo_args = [
            (name.strip().lower(), 1) for name in parts if -1 == name.find("=")
# In regex below:
#    [^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+             matches a "token" as defined by HTTP
#    "(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?"    matches a "quoted-string" as defined by HTTP, when LWS have already been replaced by a single space
# Actually, as an auth-param value can be either a token or a quoted-string, they are combined in a single pattern which matches both:
#    \"?((?<=\")(?:[^\0-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?<!\")[^\0-\x08\x0A-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+(?!\"))\"?
WWW_AUTH_STRICT = re.compile(
    r"^(?:\s*(?:,\s*)?([^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+)\s*=\s*\"?((?<=\")(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?<!\")[^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+(?!\"))\"?)(.*)$"
)
WWW_AUTH_RELAXED = re.compile(
    r"^(?:\s*(?:,\s*)?([^ \t\r\n=]+)\s*=\s*\"?((?<=\")(?:[^\\\"]|\\.)*?(?=\")|(?<!\")[^ \t\r\n,]+(?!\"))\"?)(.*)$"
)
UNQUOTE_PAIRS = re.compile(r"\\(.)")


def _parse_www_authenticate(headers, headername="www-authenticate"):
    """Returns a dictionary of dictionaries, one dict
    per auth_scheme."""
    retval = {}
    if headername in headers:
        try:

            authenticate = headers[headername].strip()
            www_auth = (
                USE_WWW_AUTH_STRICT_PARSING and WWW_AUTH_STRICT or WWW_AUTH_RELAXED
            )
            while authenticate:
                # Break off the scheme at the beginning of the line
                if headername == "authentication-info":
                    (auth_scheme, the_rest) = ("digest", authenticate)
                else:
                    (auth_scheme, the_rest) = authenticate.split(" ", 1)
                # Now loop over all the key value pairs that come after the scheme,
                # being careful not to roll into the next scheme
                match = www_auth.search(the_rest)
                auth_params = {}
                while match:
                    if match and len(match.groups()) == 3:
                        (key, value, the_rest) = match.groups()
                        auth_params[key.lower()] = UNQUOTE_PAIRS.sub(
                            r"\1", value
                        )  # '\\'.join([x.replace('\\', '') for x in value.split('\\\\')])
                    match = www_auth.search(the_rest)
                retval[auth_scheme.lower()] = auth_params
                authenticate = the_rest.strip()

        except ValueError:
            raise MalformedHeader("WWW-Authenticate")
    return retval

    if (
        "pragma" in request_headers
        and request_headers["pragma"].lower().find("no-cache") != -1
    ):
            _("Content purported to be compressed with %s but failed to decompress.")
            % response.get("content-encoding"),
    dig = _md5(
        "%s:%s"
        % (time.ctime(), ["0123456789"[random.randrange(0, 9)] for i in range(20)])
    ).hexdigest()
    return base64.b64encode(
        _sha("%s%s%s" % (cnonce, iso_now, password)).digest()
    ).strip()
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        headers["authorization"] = (
            "Basic " + base64.b64encode("%s:%s" % self.credentials).strip()
        )
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        challenge = _parse_www_authenticate(response, "www-authenticate")
        self.challenge = challenge["digest"]
        self.challenge["qop"] = (
            ("auth" in [x.strip() for x in qop.split()]) and "auth" or None
        )
            raise UnimplementedDigestAuthOptionError(
                _("Unsupported value for qop: %s." % qop)
            )
        self.A1 = "".join(
            [
                self.credentials[0],
                ":",
                self.challenge["realm"],
                ":",
                self.credentials[1],
            ]
        )
            challenge = _parse_www_authenticate(response, "www-authenticate").get(
                "digest", {}
            )
            updated_challenge = _parse_www_authenticate(
                response, "authentication-info"
            ).get("digest", {})
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        challenge = _parse_www_authenticate(response, "www-authenticate")
                _(
                    "Unsupported value for pw-algorithm: %s."
                    % self.challenge["pw-algorithm"]
                )
                self.pwhashmod.new(
                    "".join([self.credentials[1], self.challenge["salt"]])
                )
                .hexdigest()
                .lower(),
        request_digest = "%s:%s:%s:%s:%s" % (
            method,
            request_uri,
            cnonce,
            self.challenge["snonce"],
            headers_val,
        )
        request_digest = (
            hmac.new(self.key, request_digest, self.hashmod).hexdigest().lower()
        )
        challenge = _parse_www_authenticate(response, "www-authenticate").get(
            "hmacdigest", {}
        )
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        headers["X-WSSE"] = (
            'UsernameToken Username="%s", PasswordDigest="%s", '
            'Nonce="%s", Created="%s"'
        ) % (self.credentials[0], password_digest, cnonce, iso_now)
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        challenge = _parse_www_authenticate(response, "www-authenticate")
        auth = dict(
            Email=credentials[0],
            Passwd=credentials[1],
            service=service,
            source=headers["user-agent"],
        )
    def __init__(
        self, cache, safe=safename
    ):  # use safe=lambda x: md5.new(x).hexdigest() for the old behavior
        self,
        proxy_type,
        proxy_host,
        proxy_port,
        proxy_rdns=True,
        proxy_user=None,
        proxy_pass=None,
        proxy_headers=None,
            raise ProxiesUnavailableError(
                "Proxy support missing but proxy use was requested!"
            )
            proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers = (
                self.proxy_info.astuple()
            )
                        proxy_type,
                        proxy_host,
                        proxy_port,
                        proxy_rdns,
                        proxy_user,
                        proxy_pass,
                        proxy_headers,
                            % str(
                                (
                                    proxy_host,
                                    proxy_port,
                                    proxy_rdns,
                                    proxy_user,
                                    proxy_pass,
                                    proxy_headers,
                                )
                            )
                            % str(
                                (
                                    proxy_host,
                                    proxy_port,
                                    proxy_rdns,
                                    proxy_user,
                                    proxy_pass,
                                    proxy_headers,
                                )
                            )
            proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers = (
                self.proxy_info.astuple()
            )
                        proxy_type,
                        proxy_host,
                        proxy_port,
                        proxy_rdns,
                        proxy_user,
                        proxy_pass,
                        proxy_headers,
                            % str(
                                (
                                    proxy_host,
                                    proxy_port,
                                    proxy_rdns,
                                    proxy_user,
                                    proxy_pass,
                                    proxy_headers,
                                )
                            )
                            "Server presented certificate that does not match "
                            "host %s: %s" % (hostname, cert),
            except (
                ssl_SSLError,
                ssl_CertificateError,
                CertificateHostnameMismatch,
            ) as e:
                            % str(
                                (
                                    proxy_host,
                                    proxy_port,
                                    proxy_rdns,
                                    proxy_user,
                                    proxy_pass,
                                    proxy_headers,
                                )
                            )

        url,
        payload=None,
        method="GET",
        headers={},
        allow_truncated=False,
        follow_redirects=True,
        deadline=None,
        httplib.HTTPConnection.__init__(
            self, host, port=port, strict=strict, timeout=timeout
        )
            self,
            host,
            port=port,
            key_file=key_file,
            cert_file=cert_file,
            strict=strict,
            timeout=timeout,
    server_software = os.environ.get('SERVER_SOFTWARE', '')
    if (server_software.startswith('Google App Engine/') or
        server_software.startswith('Development/') or
        server_software.startswith('testutil/')):
        challenges = _parse_www_authenticate(response, "www-authenticate")
                    yield AUTH_SCHEME_CLASSES[scheme](
                        cred, host, request_uri, headers, response, content, self
                    )
        self,
        conn,
        host,
        absolute_uri,
        request_uri,
        method,
        body,
        headers,
        redirections,
        cachekey,
        auths = [
            (auth.depth(request_uri), auth)
            for auth in self.authorizations
            if auth.inscope(host, request_uri)
        ]
        (response, content) = self._conn_request(
            conn, request_uri, method, body, headers
        )
                (response, content) = self._conn_request(
                    conn, request_uri, method, body, headers
                )
            for authorization in self._auth_from_challenge(
                host, request_uri, headers, response, content
            ):
                (response, content) = self._conn_request(
                    conn, request_uri, method, body, headers
                )
        if (
            self.follow_all_redirects
            or method in self.safe_methods
            or response.status in (303, 308)
        ):
                            _(
                                "Redirected but the response is missing a Location: header."
                            ),
                            response,
                            content,
                            response["location"] = urlparse.urljoin(
                                absolute_uri, location
                            )
                    if (
                        "authorization" in headers
                        and not self.forward_authorization_headers
                    ):
                            location,
                            method=redirect_method,
                            body=body,
                            headers=headers,
                            redirections=redirections - 1,
                        "Redirected more times than rediection_limit allows.",
                        response,
                        content,
        self,
        uri,
        method="GET",
        body=None,
        headers=None,
        redirections=DEFAULT_MAX_REDIRECTS,
        connection_type=None,
        conn_key = ''
                            "Redirected more times than rediection_limit allows.",
                            {},
                            "",
                        if (
                            "etag" in info
                            and not self.ignore_etag
                            and not "if-none-match" in headers
                        ):
                        conn,
                        authority,
                        uri,
                        request_uri,
                        method,
                        body,
                        headers,
                        redirections,
                        cachekey,
                    _updateCache(
                        headers, merged_response, content, self.cache, cachekey
                    )
                        conn,
                        authority,
                        uri,
                        request_uri,
                        method,
                        body,
                        headers,
                        redirections,
                        cachekey,
                    response = Response(
                        {
                            "content-type": "text/plain",
                            "status": "408",
                            "content-length": len(content),
                        }
                    )
                    response = Response(
                        {
                            "content-type": "text/plain",
                            "status": "400",
                            "content-length": len(content),
                        }
                    )
# All exceptions raised here derive from HttpLib2Error
class HttpLib2Error(Exception):
    pass


# Some exceptions can be caught and optionally
# be turned back into responses.
class HttpLib2ErrorWithResponse(HttpLib2Error):
    def __init__(self, desc, response, content):
        self.response = response
        self.content = content
        HttpLib2Error.__init__(self, desc)


class RedirectMissingLocation(HttpLib2ErrorWithResponse):
    pass


class RedirectLimit(HttpLib2ErrorWithResponse):
    pass


class FailedToDecompressContent(HttpLib2ErrorWithResponse):
    pass


class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse):
    pass


class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse):
    pass


class MalformedHeader(HttpLib2Error):
    pass


class RelativeURIError(HttpLib2Error):
    pass


class ServerNotFoundError(HttpLib2Error):
    pass


class ProxiesUnavailableError(HttpLib2Error):
    pass


DEFAULT_TLS_VERSION = getattr(ssl, "PROTOCOL_TLS", None) or getattr(
    ssl, "PROTOCOL_SSLv23"
)
    disable_ssl_certificate_validation, ca_certs, cert_file=None, key_file=None,
    maximum_version=None, minimum_version=None, key_password=None,
    context.verify_mode = (
        ssl.CERT_NONE if disable_ssl_certificate_validation else ssl.CERT_REQUIRED
    )
            (
                _convert_byte_str(key).lower(),
                NORMALIZE_SPACE.sub(_convert_byte_str(value), " ").strip(),
            )
            tuple([x.strip().lower() for x in part.split("=", 1)])
            for part in parts
            if -1 != part.find("=")
        ]
        parts_wo_args = [
            (name.strip().lower(), 1) for name in parts if -1 == name.find("=")
# In regex below:
#    [^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+             matches a "token" as defined by HTTP
#    "(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?"    matches a "quoted-string" as defined by HTTP, when LWS have already been replaced by a single space
# Actually, as an auth-param value can be either a token or a quoted-string, they are combined in a single pattern which matches both:
#    \"?((?<=\")(?:[^\0-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?<!\")[^\0-\x08\x0A-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+(?!\"))\"?
WWW_AUTH_STRICT = re.compile(
    r"^(?:\s*(?:,\s*)?([^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+)\s*=\s*\"?((?<=\")(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?<!\")[^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+(?!\"))\"?)(.*)$"
)
WWW_AUTH_RELAXED = re.compile(
    r"^(?:\s*(?:,\s*)?([^ \t\r\n=]+)\s*=\s*\"?((?<=\")(?:[^\\\"]|\\.)*?(?=\")|(?<!\")[^ \t\r\n,]+(?!\"))\"?)(.*)$"
)
UNQUOTE_PAIRS = re.compile(r"\\(.)")


def _parse_www_authenticate(headers, headername="www-authenticate"):
    """Returns a dictionary of dictionaries, one dict
    per auth_scheme."""
    retval = {}
    if headername in headers:
        try:
            authenticate = headers[headername].strip()
            www_auth = (
                USE_WWW_AUTH_STRICT_PARSING and WWW_AUTH_STRICT or WWW_AUTH_RELAXED
            )
            while authenticate:
                # Break off the scheme at the beginning of the line
                if headername == "authentication-info":
                    (auth_scheme, the_rest) = ("digest", authenticate)
                else:
                    (auth_scheme, the_rest) = authenticate.split(" ", 1)
                # Now loop over all the key value pairs that come after the scheme,
                # being careful not to roll into the next scheme
                match = www_auth.search(the_rest)
                auth_params = {}
                while match:
                    if match and len(match.groups()) == 3:
                        (key, value, the_rest) = match.groups()
                        auth_params[key.lower()] = UNQUOTE_PAIRS.sub(
                            r"\1", value
                        )  # '\\'.join([x.replace('\\', '') for x in value.split('\\\\')])
                    match = www_auth.search(the_rest)
                retval[auth_scheme.lower()] = auth_params
                authenticate = the_rest.strip()
        except ValueError:
            raise MalformedHeader("WWW-Authenticate")
    return retval

    if (
        "pragma" in request_headers
        and request_headers["pragma"].lower().find("no-cache") != -1
    ):
            _("Content purported to be compressed with %s but failed to decompress.")
            % response.get("content-encoding"),
                headers = header.Header(
                    v, maxlinelen=self._maxheaderlen, charset="utf-8", header_name=h
                )
            text = b"".join(
                [status_header.encode("utf-8"), header_str.encode("utf-8"), content]
            )
        (
            "%s:%s"
            % (time.ctime(), ["0123456789"[random.randrange(0, 9)] for i in range(20)])
        ).encode("utf-8")
    return base64.b64encode(
        _sha(("%s%s%s" % (cnonce, iso_now, password)).encode("utf-8")).digest()
    ).strip().decode("utf-8")
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        challenge = _parse_www_authenticate(response, "www-authenticate")
        self.challenge = challenge["digest"]
        self.challenge["qop"] = (
            ("auth" in [x.strip() for x in qop.split()]) and "auth" or None
        )
            raise UnimplementedDigestAuthOptionError(
                _("Unsupported value for qop: %s." % qop)
            )
        self.A1 = "".join(
            [
                self.credentials[0],
                ":",
                self.challenge["realm"],
                ":",
                self.credentials[1],
            ]
        )
            challenge = _parse_www_authenticate(response, "www-authenticate").get(
                "digest", {}
            )
            updated_challenge = _parse_www_authenticate(
                response, "authentication-info"
            ).get("digest", {})
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        challenge = _parse_www_authenticate(response, "www-authenticate")
                _(
                    "Unsupported value for pw-algorithm: %s."
                    % self.challenge["pw-algorithm"]
                )
                self.pwhashmod.new(
                    "".join([self.credentials[1], self.challenge["salt"]])
                )
                .hexdigest()
                .lower(),
        request_digest = "%s:%s:%s:%s:%s" % (
            method,
            request_uri,
            cnonce,
            self.challenge["snonce"],
            headers_val,
        )
        request_digest = (
            hmac.new(self.key, request_digest, self.hashmod).hexdigest().lower()
        )
        challenge = _parse_www_authenticate(response, "www-authenticate").get(
            "hmacdigest", {}
        )
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        headers["X-WSSE"] = (
            'UsernameToken Username="%s", PasswordDigest="%s", '
            'Nonce="%s", Created="%s"'
        ) % (self.credentials[0], password_digest, cnonce, iso_now)
    def __init__(
        self, credentials, host, request_uri, headers, response, content, http
    ):
        Authentication.__init__(
            self, credentials, host, request_uri, headers, response, content, http
        )
        challenge = _parse_www_authenticate(response, "www-authenticate")
        auth = dict(
            Email=credentials[0],
            Passwd=credentials[1],
            service=service,
            source=headers["user-agent"],
        )
    def __init__(
        self, cache, safe=safename
    ):  # use safe=lambda x: md5.new(x).hexdigest() for the old behavior
        self,
        proxy_type,
        proxy_host,
        proxy_port,
        proxy_rdns=True,
        proxy_user=None,
        proxy_pass=None,
        proxy_headers=None,
        self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_user, self.proxy_pass, self.proxy_headers = (
            raise ProxiesUnavailableError(
                "Proxy support missing but proxy use was requested!"
            )
            proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers = (
                self.proxy_info.astuple()
            )
                        proxy_type,
                        proxy_host,
                        proxy_port,
                        proxy_rdns,
                        proxy_user,
                        proxy_pass,
                    print(
                        "connect: ({0}, {1}) ************".format(self.host, self.port)
                    )
                                str(
                                    (
                                        proxy_host,
                                        proxy_port,
                                        proxy_rdns,
                                        proxy_user,
                                        proxy_pass,
                                        proxy_headers,
                                    )
                                )
                                str(
                                    (
                                        proxy_host,
                                        proxy_port,
                                        proxy_rdns,
                                        proxy_user,
                                        proxy_pass,
                                        proxy_headers,
                                    )
                                )
            self.disable_ssl_certificate_validation, self.ca_certs, cert_file, key_file,
            maximum_version=tls_maximum_version, minimum_version=tls_minimum_version,
            host,
            port=port,
            timeout=timeout,
            context=context,
            proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers = (
                self.proxy_info.astuple()
            )
                        proxy_type,
                        proxy_host,
                        proxy_port,
                        proxy_rdns,
                        proxy_user,
                        proxy_pass,
                if (
                    not hasattr(self._context, "check_hostname")
                    and not self.disable_ssl_certificate_validation
                ):
                                str(
                                    (
                                        proxy_host,
                                        proxy_port,
                                        proxy_rdns,
                                        proxy_user,
                                        proxy_pass,
                                        proxy_headers,
                                    )
                                )
                                str(
                                    (
                                        proxy_host,
                                        proxy_port,
                                        proxy_rdns,
                                        proxy_user,
                                        proxy_pass,
                                        proxy_headers,
                                    )
                                )
        challenges = _parse_www_authenticate(response, "www-authenticate")
                    yield AUTH_SCHEME_CLASSES[scheme](
                        cred, host, request_uri, headers, response, content, self
                    )
                errno_ = (
                    e.args[0].errno if isinstance(e.args[0], socket.error) else e.errno
                )
        self,
        conn,
        host,
        absolute_uri,
        request_uri,
        method,
        body,
        headers,
        redirections,
        cachekey,
        auths = [
            (auth.depth(request_uri), auth)
            for auth in self.authorizations
            if auth.inscope(host, request_uri)
        ]
        (response, content) = self._conn_request(
            conn, request_uri, method, body, headers
        )
                (response, content) = self._conn_request(
                    conn, request_uri, method, body, headers
                )
            for authorization in self._auth_from_challenge(
                host, request_uri, headers, response, content
            ):
                (response, content) = self._conn_request(
                    conn, request_uri, method, body, headers
                )
        if (
            self.follow_all_redirects
            or method in self.safe_methods
            or response.status in (303, 308)
        ):
                            _(
                                "Redirected but the response is missing a Location: header."
                            ),
                            response,
                            content,
                            response["location"] = urllib.parse.urljoin(
                                absolute_uri, location
                            )
                    if (
                        "authorization" in headers
                        and not self.forward_authorization_headers
                    ):
                            location,
                            method=redirect_method,
                            body=body,
                            headers=headers,
                            redirections=redirections - 1,
                        "Redirected more times than redirection_limit allows.",
                        response,
                        content,
        self,
        uri,
        method="GET",
        body=None,
        headers=None,
        redirections=DEFAULT_MAX_REDIRECTS,
        connection_type=None,
        conn_key = ''
                                info.replace_header(
                                    k, str(*email.header.decode_header(v)[0])
                                )
                            "Redirected more times than redirection_limit allows.",
                            {},
                            "",
                        if (
                            "etag" in info
                            and not self.ignore_etag
                            and not "if-none-match" in headers
                        ):
                        conn,
                        authority,
                        uri,
                        request_uri,
                        method,
                        body,
                        headers,
                        redirections,
                        cachekey,
                    _updateCache(
                        headers, merged_response, content, self.cache, cachekey
                    )
                        conn,
                        authority,
                        uri,
                        request_uri,
                        method,
                        body,
                        headers,
                        redirections,
                        cachekey,
                    response = Response(
                        {
                            "content-type": "text/plain",
                            "status": "408",
                            "content-length": len(content),
                        }
                    )
                    response = Response(
                        {
                            "content-type": "text/plain",
                            "status": "400",
                            "content-length": len(content),
                        }
                    )
	local version_setup=$(fgrep 'VERSION =' setup.py |tr -d " '\"" |cut -d\= -f2)
	local version_py2=$(cd python2 ; python2 -Es -c 'import httplib2;print(httplib2.__version__)')
	local version_py3=$(cd python3 ; python3 -Es -c 'import httplib2;print(httplib2.__version__)')
		echo "error: setup.py VERSION=$version_setup expected=$need" >&1
	version_source=$(cd python3 ; python3 -Es -c 'import httplib2;print(httplib2.__version__)')
	version_installed=$($pip show httplib2 |fgrep Version |cut -d' ' -f2)
        args = ['--forked', '--fulltrace', '--no-cov', 'tests/']
            args += ['-k', self.test_suite]
        sys.stderr.write('setup.py:test run pytest {}\n'.format(' '.join(args)))
    with open(os.path.join(project_root, name), 'rb') as f:
        g = (line.decode('utf-8').lstrip().split('#', 1)[0].rstrip() for line in f)
        assert re.match(
            br".+ HTTP/\d\.\d\r\n$", start_line
        ), "Start line does not look like HTTP request: " + repr(start_line)
        self,
        host,
        port=None,
        key_file=None,
        cert_file=None,
        strict=None,
        timeout=None,
        proxy_info=None,
        self,
        host,
        port=None,
        key_file=None,
        cert_file=None,
        strict=None,
        timeout=None,
        proxy_info=None,
                gresult[0] = Exception(
                    "Request count expected={0} actual={1}".format(
                        request_count, gcounter[0]
                    )
                )
    if (
        not undefined_body_length
        and proto != "HTTP/1.0"
        and "content-length" not in headers
    ):
        raise Exception(
            "httplib2.tests.http_response_bytes: client could not figure response body length"
        )
        "{proto} {status}\r\n{headers}\r\n".format(
            proto=proto, status=status, headers=header_string
        ).encode()
        + body
def http_parse_auth(s):
    """https://tools.ietf.org/html/rfc7235#section-2.1
    """
    scheme, rest = s.split(" ", 1)
    result = {}
    while True:
        m = httplib2.WWW_AUTH_RELAXED.search(rest)
        if not m:
            break
        if len(m.groups()) == 3:
            key, value, rest = m.groups()
            result[key.lower()] = httplib2.UNQUOTE_PAIRS.sub(r"\1", value)
    return result


        if " " not in auth_header:
        scheme, data = auth_header.split(" ", 1)
        scheme = scheme.lower()
            decoded = base64.b64decode(data).decode()
            auth_info = http_parse_auth(data)
                    body="auth-info opaque mismatch expected={} actual={}".format(
                        server_opaque, client_opaque
                    ).encode()
                ha1 = hasher(
                    ":".join((allow_username, realm, allow_password)).encode()
                ).hexdigest()
                    ":".join(
                        (ha1, client_nonce, client_nc, client_cnonce, client_qop, ha2)
                    ).encode()
                    ":".join(
                        (
                            ha1,
                            client_nonce,
                            client_nc,
                            client_cnonce,
                            client_qop,
                            rspauth_ha2,
                        )
                    ).encode()
                        "authentication-info": " ".join(
                            )
            auth_info = http_parse_auth(x_wsse)
            client_username = auth_info.get("username", "")
            client_nonce = auth_info.get("nonce", "")
            client_created = auth_info.get("created", "")
            client_digest = auth_info.get("passworddigest", "")
            print("$$$ check client={} == real={}".format(client_digest, digest_b64))
    handler = tests.http_reflect_with_auth(
        allow_scheme="basic", allow_credentials=(("joe", password),)
    )
    handler = tests.http_reflect_with_auth(
        allow_scheme="basic", allow_credentials=(("joe", password),)
    )
    handler = tests.http_reflect_with_auth(
        allow_scheme="basic", allow_credentials=allowed
    )
    handler = tests.http_reflect_with_auth(
        allow_scheme="digest", allow_credentials=(("joe", password),)
    )
        allow_scheme="digest",
        allow_credentials=(("joe", password),),
        out_renew_nonce=grenew_nonce,
        info = httplib2._parse_www_authenticate(response1, "authentication-info")
        assert info.get("digest", {}).get("nc") == "00000001", info
        info2 = httplib2._parse_www_authenticate(response2, "authentication-info")
        assert info2.get("digest", {}).get("nc") == "00000002", info2
        info3 = httplib2._parse_www_authenticate(response3, "authentication-info")
        assert info3.get("digest", {}).get("nc") == "00000001", info3
        info = httplib2._parse_www_authenticate(
            requests[0][1].headers, "www-authenticate"
        )
        info2 = httplib2._parse_www_authenticate(
            requests[2][1].headers, "www-authenticate"
        )
            {
                "www-authenticate": 'Test realm="test realm" , foo=foo ,bar="bar", baz=baz,qux=qux'
            },
            {
                "test": {
                    "realm": "test realm",
                    "foo": "foo",
                    "bar": "bar",
                    "baz": "baz",
                    "qux": "qux",
                }
            },
        (
            {"www-authenticate": 'Test realm="a \\"test\\" realm"'},
            {"test": {"realm": 'a "test" realm'}},
        ),
        (
            {"www-authenticate": 'Basic realm="me", algorithm="MD5"'},
            {"basic": {"realm": "me", "algorithm": "MD5"}},
        ),
        (
            {"www-authenticate": 'Basic realm="me", algorithm=MD5'},
            {"basic": {"realm": "me", "algorithm": "MD5"}},
        ),
        (
            {"www-authenticate": 'Basic realm="me",other="fred" '},
            {"basic": {"realm": "me", "other": "fred"}},
        ),
            {
                "www-authenticate": 'Digest realm="digest1", qop="auth,auth-int", nonce="7102dd2", opaque="e9517f"'
            },
            {
                "digest": {
                    "realm": "digest1",
                    "qop": "auth,auth-int",
                    "nonce": "7102dd2",
                    "opaque": "e9517f",
                }
            },
        # multiple schema choice
            {
                "www-authenticate": 'Digest realm="multi-d", nonce="8b11d0f6", opaque="cc069c" Basic realm="multi-b" '
            },
            {
                "digest": {"realm": "multi-d", "nonce": "8b11d0f6", "opaque": "cc069c"},
                "basic": {"realm": "multi-b"},
            },
        # FIXME
        # comma between schemas (glue for multiple headers with same name)
        # ({'www-authenticate': 'Digest realm="2-comma-d", qop="auth-int", nonce="c0c8ff1", Basic realm="2-comma-b"'},
        #  {'digest': {'realm': '2-comma-d', 'qop': 'auth-int', 'nonce': 'c0c8ff1'},
        #   'basic': {'realm': '2-comma-b'}}),
        # FIXME
        # ({'www-authenticate': 'Digest realm="com3d", Basic realm="com3b", WSSE realm="com3w", profile="token"'},
        #  {'digest': {'realm': 'com3d'}, 'basic': {'realm': 'com3b'}, 'wsse': {'realm': 'com3w', profile': 'token'}}),
            {
                "www-authenticate": 'Digest realm="myrealm", nonce="KBAA=3", algorithm=MD5, qop="auth", stale=true'
            },
            {
                "digest": {
                    "realm": "myrealm",
                    "nonce": "KBAA=3",
                    "algorithm": "MD5",
                    "qop": "auth",
                    "stale": "true",
                }
            },
        assert httplib2._parse_www_authenticate(headers) == info
def test_parse_www_authenticate_malformed():
    with tests.assert_raises(httplib2.MalformedHeader):
        httplib2._parse_www_authenticate(
            {
                "www-authenticate": 'OAuth "Facebook Platform" "invalid_token" "Invalid OAuth access token."'
            }
        )
    response = {
        "www-authenticate": 'Digest realm="myrealm", nonce="KBAA=35", algorithm=MD5, qop="auth"'
    }
    d = httplib2.DigestAuthentication(
        credentials, host, request_uri, headers, response, content, None
    )
        'nonce="KBAA=35", uri="/test/digest/"'
        + ', algorithm=MD5, response="de6d4a123b80801d0e94550411b6283f", '
        "www-authenticate": 'Digest realm="myrealm", nonce="30352fd", algorithm=MD5, '
        'qop="auth", opaque="atestopaque"'
    d = httplib2.DigestAuthentication(
        credentials, host, request_uri, headers, response, content, None
    )
    response["www-authenticate"] = (
        'Digest realm="myrealm", nonce="bd669f", '
        'algorithm=MD5, qop="auth", stale=true'
    )
    d = httplib2.DigestAuthentication(
        credentials, host, request_uri, headers, response, content, None
    )
    response["www-authenticate"] = (
        'Digest realm="myrealm", nonce="barney", '
        'algorithm=MD5, qop="auth", stale=true'
    )
    d = httplib2.DigestAuthentication(
        credentials, host, request_uri, headers, response, content, None
    )

Fixed code:
from httplib2 import auth
from httplib2.error import *
def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation, ca_certs, ssl_version, hostname, key_password):
            sock, keyfile=key_file, certfile=cert_file, cert_reqs=cert_reqs, ca_certs=ca_certs, ssl_version=ssl_version,

    return dict([(key.lower(), NORMALIZE_SPACE.sub(value, " ").strip()) for (key, value) in headers.iteritems()])
            tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=")
        parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")]
    if "pragma" in request_headers and request_headers["pragma"].lower().find("no-cache") != -1:
            _("Content purported to be compressed with %s but failed to decompress.") % response.get("content-encoding"),
    dig = _md5("%s:%s" % (time.ctime(), ["0123456789"[random.randrange(0, 9)] for i in range(20)])).hexdigest()
    return base64.b64encode(_sha("%s%s%s" % (cnonce, iso_now, password)).digest()).strip()
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        headers["authorization"] = "Basic " + base64.b64encode("%s:%s" % self.credentials).strip()
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        self.challenge = auth._parse_www_authenticate(response, "www-authenticate")["digest"]
        self.challenge["qop"] = ("auth" in [x.strip() for x in qop.split()]) and "auth" or None
            raise UnimplementedDigestAuthOptionError(_("Unsupported value for qop: %s." % qop))
        self.A1 = "".join([self.credentials[0], ":", self.challenge["realm"], ":", self.credentials[1],])
            challenge = auth._parse_www_authenticate(response, "www-authenticate").get("digest", {})
            updated_challenge = auth._parse_authentication_info(response, "authentication-info")
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        challenge = auth._parse_www_authenticate(response, "www-authenticate")
                _("Unsupported value for pw-algorithm: %s." % self.challenge["pw-algorithm"])
                self.pwhashmod.new("".join([self.credentials[1], self.challenge["salt"]])).hexdigest().lower(),
        request_digest = "%s:%s:%s:%s:%s" % (method, request_uri, cnonce, self.challenge["snonce"], headers_val,)
        request_digest = hmac.new(self.key, request_digest, self.hashmod).hexdigest().lower()
        challenge = auth._parse_www_authenticate(response, "www-authenticate").get("hmacdigest", {})
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        headers["X-WSSE"] = ('UsernameToken Username="%s", PasswordDigest="%s", ' 'Nonce="%s", Created="%s"') % (
            self.credentials[0],
            password_digest,
            cnonce,
            iso_now,
        )
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        challenge = auth._parse_www_authenticate(response, "www-authenticate")
        auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers["user-agent"],)
    def __init__(self, cache, safe=safename):  # use safe=lambda x: md5.new(x).hexdigest() for the old behavior

        self, proxy_type, proxy_host, proxy_port, proxy_rdns=True, proxy_user=None, proxy_pass=None, proxy_headers=None,
            raise ProxiesUnavailableError("Proxy support missing but proxy use was requested!")
            (
                proxy_type,
                proxy_host,
                proxy_port,
                proxy_rdns,
                proxy_user,
                proxy_pass,
                proxy_headers,
            ) = self.proxy_info.astuple()
                        proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,
                            % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,))
                            % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,))
            (
                proxy_type,
                proxy_host,
                proxy_port,
                proxy_rdns,
                proxy_user,
                proxy_pass,
                proxy_headers,
            ) = self.proxy_info.astuple()
                        proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,
                            % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,))
                            "Server presented certificate that does not match " "host %s: %s" % (hostname, cert),
            except (ssl_SSLError, ssl_CertificateError, CertificateHostnameMismatch,) as e:
                            % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,))
        url, payload=None, method="GET", headers={}, allow_truncated=False, follow_redirects=True, deadline=None,
        httplib.HTTPConnection.__init__(self, host, port=port, strict=strict, timeout=timeout)
            self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict, timeout=timeout,
    server_software = os.environ.get("SERVER_SOFTWARE", "")
    if (
        server_software.startswith("Google App Engine/")
        or server_software.startswith("Development/")
        or server_software.startswith("testutil/")
    ):

        challenges = auth._parse_www_authenticate(response, "www-authenticate")
                    yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self)
        self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey,
        auths = [(auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri)]
        (response, content) = self._conn_request(conn, request_uri, method, body, headers)
                (response, content) = self._conn_request(conn, request_uri, method, body, headers)
            for authorization in self._auth_from_challenge(host, request_uri, headers, response, content):
                (response, content) = self._conn_request(conn, request_uri, method, body, headers)
        if self.follow_all_redirects or method in self.safe_methods or response.status in (303, 308):
                            _("Redirected but the response is missing a Location: header."), response, content,
                            response["location"] = urlparse.urljoin(absolute_uri, location)
                    if "authorization" in headers and not self.forward_authorization_headers:
                            location, method=redirect_method, body=body, headers=headers, redirections=redirections - 1,
                        "Redirected more times than rediection_limit allows.", response, content,
        self, uri, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None,
        conn_key = ""
                            "Redirected more times than rediection_limit allows.", {}, "",
                        if "etag" in info and not self.ignore_etag and not "if-none-match" in headers:
                        conn, authority, uri, request_uri, method, body, headers, redirections, cachekey,
                    _updateCache(headers, merged_response, content, self.cache, cachekey)
                        conn, authority, uri, request_uri, method, body, headers, redirections, cachekey,
                    response = Response({"content-type": "text/plain", "status": "408", "content-length": len(content),})
                    response = Response({"content-type": "text/plain", "status": "400", "content-length": len(content),})
import base64
import re

import pyparsing as pp

from .error import *

UNQUOTE_PAIRS = re.compile(r"\\(.)")
unquote = lambda s, l, t: UNQUOTE_PAIRS.sub(r"\1", t[0][1:-1])

# https://tools.ietf.org/html/rfc7235#section-1.2
# https://tools.ietf.org/html/rfc7235#appendix-B
tchar = "!#$%&'*+-.^_`|~" + pp.nums + pp.alphas
token = pp.Word(tchar).setName("token")
token68 = pp.Combine(pp.Word("-._~+/" + pp.nums + pp.alphas) + pp.ZeroOrMore("=")).setName("token68")

quoted_string = pp.dblQuotedString.copy().setName("quoted-string").setParseAction(unquote)
auth_param_name = token.copy().setName("auth-param-name").addParseAction(pp.downcaseTokens)
auth_param = auth_param_name + pp.Suppress("=") + (token ^ quoted_string)
params = pp.Dict(pp.delimitedList(pp.Group(auth_param)))

scheme = token("scheme")
challenge = scheme + (token68("token") ^ params("params"))

authentication_info = params.copy()
www_authenticate = pp.delimitedList(pp.Group(challenge))


def _parse_authentication_info(headers, headername="authentication-info"):
    """https://tools.ietf.org/html/rfc7615
    """
    header = headers.get(headername, "").strip()
    if not header:
        return {}
    try:
        parsed = authentication_info.parseString(header)
    except pp.ParseException as ex:
        # print(ex.explain(ex))
        raise MalformedHeader(headername)

    return parsed.asDict()


def _parse_www_authenticate(headers, headername="www-authenticate"):
    """Returns a dictionary of dictionaries, one dict per auth_scheme."""
    header = headers.get(headername, "").strip()
    if not header:
        return {}
    try:
        parsed = www_authenticate.parseString(header)
    except pp.ParseException as ex:
        # print(ex.explain(ex))
        raise MalformedHeader(headername)

    retval = {
        challenge["scheme"].lower(): challenge["params"].asDict()
        if "params" in challenge
        else {"token": challenge.get("token")}
        for challenge in parsed
    }
    return retval
# All exceptions raised here derive from HttpLib2Error
class HttpLib2Error(Exception):
    pass


# Some exceptions can be caught and optionally
# be turned back into responses.
class HttpLib2ErrorWithResponse(HttpLib2Error):
    def __init__(self, desc, response, content):
        self.response = response
        self.content = content
        HttpLib2Error.__init__(self, desc)


class RedirectMissingLocation(HttpLib2ErrorWithResponse):
    pass


class RedirectLimit(HttpLib2ErrorWithResponse):
    pass


class FailedToDecompressContent(HttpLib2ErrorWithResponse):
    pass


class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse):
    pass


class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse):
    pass


class MalformedHeader(HttpLib2Error):
    pass


class RelativeURIError(HttpLib2Error):
    pass


class ServerNotFoundError(HttpLib2Error):
    pass


class ProxiesUnavailableError(HttpLib2Error):
    pass
from . import auth
from .error import *

DEFAULT_TLS_VERSION = getattr(ssl, "PROTOCOL_TLS", None) or getattr(ssl, "PROTOCOL_SSLv23")

    disable_ssl_certificate_validation,
    ca_certs,
    cert_file=None,
    key_file=None,
    maximum_version=None,
    minimum_version=None,
    key_password=None,
    context.verify_mode = ssl.CERT_NONE if disable_ssl_certificate_validation else ssl.CERT_REQUIRED
            (_convert_byte_str(key).lower(), NORMALIZE_SPACE.sub(_convert_byte_str(value), " ").strip(),)
            tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=")
        parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")]
    if "pragma" in request_headers and request_headers["pragma"].lower().find("no-cache") != -1:
            _("Content purported to be compressed with %s but failed to decompress.") % response.get("content-encoding"),
                headers = header.Header(v, maxlinelen=self._maxheaderlen, charset="utf-8", header_name=h)
            text = b"".join([status_header.encode("utf-8"), header_str.encode("utf-8"), content])
        ("%s:%s" % (time.ctime(), ["0123456789"[random.randrange(0, 9)] for i in range(20)])).encode("utf-8")
    return (
        base64.b64encode(_sha(("%s%s%s" % (cnonce, iso_now, password)).encode("utf-8")).digest()).strip().decode("utf-8")
    )
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        self.challenge = auth._parse_www_authenticate(response, "www-authenticate")["digest"]
        self.challenge["qop"] = ("auth" in [x.strip() for x in qop.split()]) and "auth" or None
            raise UnimplementedDigestAuthOptionError(_("Unsupported value for qop: %s." % qop))
        self.A1 = "".join([self.credentials[0], ":", self.challenge["realm"], ":", self.credentials[1],])
            challenge = auth._parse_www_authenticate(response, "www-authenticate").get("digest", {})
            updated_challenge = auth._parse_authentication_info(response, "authentication-info")
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        challenge = auth._parse_www_authenticate(response, "www-authenticate")
                _("Unsupported value for pw-algorithm: %s." % self.challenge["pw-algorithm"])
                self.pwhashmod.new("".join([self.credentials[1], self.challenge["salt"]])).hexdigest().lower(),
        request_digest = "%s:%s:%s:%s:%s" % (method, request_uri, cnonce, self.challenge["snonce"], headers_val,)
        request_digest = hmac.new(self.key, request_digest, self.hashmod).hexdigest().lower()
        challenge = auth._parse_www_authenticate(response, "www-authenticate").get("hmacdigest", {})
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        headers["X-WSSE"] = ('UsernameToken Username="%s", PasswordDigest="%s", ' 'Nonce="%s", Created="%s"') % (
            self.credentials[0],
            password_digest,
            cnonce,
            iso_now,
        )
    def __init__(self, credentials, host, request_uri, headers, response, content, http):
        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
        challenge = auth._parse_www_authenticate(response, "www-authenticate")
        auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers["user-agent"],)
    def __init__(self, cache, safe=safename):  # use safe=lambda x: md5.new(x).hexdigest() for the old behavior

        self, proxy_type, proxy_host, proxy_port, proxy_rdns=True, proxy_user=None, proxy_pass=None, proxy_headers=None,
        (
            self.proxy_type,
            self.proxy_host,
            self.proxy_port,
            self.proxy_rdns,
            self.proxy_user,
            self.proxy_pass,
            self.proxy_headers,
        ) = (
            raise ProxiesUnavailableError("Proxy support missing but proxy use was requested!")
            (
                proxy_type,
                proxy_host,
                proxy_port,
                proxy_rdns,
                proxy_user,
                proxy_pass,
                proxy_headers,
            ) = self.proxy_info.astuple()
                        proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass,
                    print("connect: ({0}, {1}) ************".format(self.host, self.port))
                                str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,))
                                str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,))
            self.disable_ssl_certificate_validation,
            self.ca_certs,
            cert_file,
            key_file,
            maximum_version=tls_maximum_version,
            minimum_version=tls_minimum_version,
            host, port=port, timeout=timeout, context=context,
            (
                proxy_type,
                proxy_host,
                proxy_port,
                proxy_rdns,
                proxy_user,
                proxy_pass,
                proxy_headers,
            ) = self.proxy_info.astuple()
                        proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass,
                if not hasattr(self._context, "check_hostname") and not self.disable_ssl_certificate_validation:
                                str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,))
                                str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,))
        challenges = auth._parse_www_authenticate(response, "www-authenticate")
                    yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self)
                errno_ = e.args[0].errno if isinstance(e.args[0], socket.error) else e.errno
        self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey,
        auths = [(auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri)]
        (response, content) = self._conn_request(conn, request_uri, method, body, headers)
                (response, content) = self._conn_request(conn, request_uri, method, body, headers)
            for authorization in self._auth_from_challenge(host, request_uri, headers, response, content):
                (response, content) = self._conn_request(conn, request_uri, method, body, headers)
        if self.follow_all_redirects or method in self.safe_methods or response.status in (303, 308):
                            _("Redirected but the response is missing a Location: header."), response, content,
                            response["location"] = urllib.parse.urljoin(absolute_uri, location)
                    if "authorization" in headers and not self.forward_authorization_headers:
                            location, method=redirect_method, body=body, headers=headers, redirections=redirections - 1,
                        "Redirected more times than redirection_limit allows.", response, content,
        self, uri, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None,
        conn_key = ""
                                info.replace_header(k, str(*email.header.decode_header(v)[0]))
                            "Redirected more times than redirection_limit allows.", {}, "",
                        if "etag" in info and not self.ignore_etag and not "if-none-match" in headers:
                        conn, authority, uri, request_uri, method, body, headers, redirections, cachekey,
                    _updateCache(headers, merged_response, content, self.cache, cachekey)
                        conn, authority, uri, request_uri, method, body, headers, redirections, cachekey,
                    response = Response({"content-type": "text/plain", "status": "408", "content-length": len(content),})
                    response = Response({"content-type": "text/plain", "status": "400", "content-length": len(content),})
import base64
import re

import pyparsing as pp

from .error import *

UNQUOTE_PAIRS = re.compile(r"\\(.)")
unquote = lambda s, l, t: UNQUOTE_PAIRS.sub(r"\1", t[0][1:-1])

# https://tools.ietf.org/html/rfc7235#section-1.2
# https://tools.ietf.org/html/rfc7235#appendix-B
tchar = "!#$%&'*+-.^_`|~" + pp.nums + pp.alphas
token = pp.Word(tchar).setName("token")
token68 = pp.Combine(pp.Word("-._~+/" + pp.nums + pp.alphas) + pp.ZeroOrMore("=")).setName("token68")

quoted_string = pp.dblQuotedString.copy().setName("quoted-string").setParseAction(unquote)
auth_param_name = token.copy().setName("auth-param-name").addParseAction(pp.downcaseTokens)
auth_param = auth_param_name + pp.Suppress("=") + (token ^ quoted_string)
params = pp.Dict(pp.delimitedList(pp.Group(auth_param)))

scheme = token("scheme")
challenge = scheme + (token68("token") ^ params("params"))

authentication_info = params.copy()
www_authenticate = pp.delimitedList(pp.Group(challenge))


def _parse_authentication_info(headers, headername="authentication-info"):
    """https://tools.ietf.org/html/rfc7615
    """
    header = headers.get(headername, "").strip()
    if not header:
        return {}
    try:
        parsed = authentication_info.parseString(header)
    except pp.ParseException as ex:
        # print(ex.explain(ex))
        raise MalformedHeader(headername)

    return parsed.asDict()


def _parse_www_authenticate(headers, headername="www-authenticate"):
    """Returns a dictionary of dictionaries, one dict per auth_scheme."""
    header = headers.get(headername, "").strip()
    if not header:
        return {}
    try:
        parsed = www_authenticate.parseString(header)
    except pp.ParseException as ex:
        # print(ex.explain(ex))
        raise MalformedHeader(headername)

    retval = {
        challenge["scheme"].lower(): challenge["params"].asDict()
        if "params" in challenge
        else {"token": challenge.get("token")}
        for challenge in parsed
    }
    return retval
# All exceptions raised here derive from HttpLib2Error
class HttpLib2Error(Exception):
    pass


# Some exceptions can be caught and optionally
# be turned back into responses.
class HttpLib2ErrorWithResponse(HttpLib2Error):
    def __init__(self, desc, response, content):
        self.response = response
        self.content = content
        HttpLib2Error.__init__(self, desc)


class RedirectMissingLocation(HttpLib2ErrorWithResponse):
    pass


class RedirectLimit(HttpLib2ErrorWithResponse):
    pass


class FailedToDecompressContent(HttpLib2ErrorWithResponse):
    pass


class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse):
    pass


class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse):
    pass


class MalformedHeader(HttpLib2Error):
    pass


class RelativeURIError(HttpLib2Error):
    pass


class ServerNotFoundError(HttpLib2Error):
    pass


class ProxiesUnavailableError(HttpLib2Error):
    pass
pyparsing>=2.4.2,<3 # TODO include v3 after dropping Python2 support
	local version_setup=$(python setup.py --version)
	local version_py2=$(python -Es -c "$(egrep '^__version__' python2/httplib2/__init__.py);print(__version__)")
	local version_py3=$(python -Es -c "$(egrep '^__version__' python3/httplib2/__init__.py);print(__version__)")
		echo "error: setup.py version=$version_setup expected=$need" >&1
	version_source=$(python setup.py --version)
	version_installed=$($pip show httplib2 |fgrep Version: |cut -d' ' -f2)

        args = ["--forked", "--fulltrace", "--no-cov", "tests/"]
            args += ["-k", self.test_suite]
        sys.stderr.write("setup.py:test run pytest {}\n".format(" ".join(args)))
    with open(os.path.join(project_root, name), "rb") as f:
        g = (line.decode("utf-8").lstrip().split("#", 1)[0].rstrip() for line in f)
    install_requires=read_requirements("requirements.txt"),
        assert re.match(br".+ HTTP/\d\.\d\r\n$", start_line), "Start line does not look like HTTP request: " + repr(
            start_line
        )
        self, host, port=None, key_file=None, cert_file=None, strict=None, timeout=None, proxy_info=None,
        self, host, port=None, key_file=None, cert_file=None, strict=None, timeout=None, proxy_info=None,
                gresult[0] = Exception("Request count expected={0} actual={1}".format(request_count, gcounter[0]))
    if not undefined_body_length and proto != "HTTP/1.0" and "content-length" not in headers:
        raise Exception("httplib2.tests.http_response_bytes: client could not figure response body length")
        "{proto} {status}\r\n{headers}\r\n".format(proto=proto, status=status, headers=header_string).encode() + body
        try:
            auth_parsed = httplib2.auth._parse_www_authenticate(request.headers, "authorization")
            print("debug: auth_parsed", auth_parsed)
        except httplib2.error.MalformedHeader:
            print("debug: auth header error")
        scheme = auth_header.split(" ", 1)[0].lower()
        print("debug: first auth scheme='{}'".format(scheme))
        auth_info = auth_parsed[scheme]
            decoded = base64.b64decode(auth_info["token"]).decode()
                    body="auth-info opaque mismatch expected={} actual={}".format(server_opaque, client_opaque).encode()
                ha1 = hasher(":".join((allow_username, realm, allow_password)).encode()).hexdigest()
                    ":".join((ha1, client_nonce, client_nc, client_cnonce, client_qop, ha2)).encode()
                    ":".join((ha1, client_nonce, client_nc, client_cnonce, client_qop, rspauth_ha2,)).encode()
                        "authentication-info": ", ".join(filter(None,
                            ))
            wsse_params = httplib2.auth._parse_www_authenticate(request.headers, "x-wsse").get("usernametoken", {})
            print("debug: wsse_params", wsse_params)
            client_username = wsse_params.get("username", "")
            client_nonce = wsse_params.get("nonce", "")
            client_created = wsse_params.get("created", "")
            client_digest = wsse_params.get("passworddigest", "")
            print("debug: check client={} == real={}".format(client_digest, digest_b64))
import time

    handler = tests.http_reflect_with_auth(allow_scheme="basic", allow_credentials=(("joe", password),))
    handler = tests.http_reflect_with_auth(allow_scheme="basic", allow_credentials=(("joe", password),))
    handler = tests.http_reflect_with_auth(allow_scheme="basic", allow_credentials=allowed)
    handler = tests.http_reflect_with_auth(allow_scheme="digest", allow_credentials=(("joe", password),))
        allow_scheme="digest", allow_credentials=(("joe", password),), out_renew_nonce=grenew_nonce,
        info = httplib2.auth._parse_authentication_info(response1)
        print("debug: response1 authentication-info: {}\nparsed: {}".format(response1.get("authentication-info"), info))
        assert info.get("nc") == "00000001", info
        info2 = httplib2.auth._parse_authentication_info(response2)
        assert info2.get("nc") == "00000002", info2
        info3 = httplib2.auth._parse_authentication_info(response3)
        assert info3.get("nc") == "00000001", info3
        info = httplib2.auth._parse_www_authenticate(requests[0][1].headers, "www-authenticate")
        info2 = httplib2.auth._parse_www_authenticate(requests[2][1].headers, "www-authenticate")
            {"www-authenticate": 'Test realm="test realm" , foo=foo ,bar="bar", baz=baz,qux=qux'},
            {"test": {"realm": "test realm", "foo": "foo", "bar": "bar", "baz": "baz", "qux": "qux"}},
        ({"www-authenticate": 'Test realm="a \\"test\\" realm"'}, {"test": {"realm": 'a "test" realm'}},),
        ({"www-authenticate": 'Basic realm="me", algorithm="MD5"'}, {"basic": {"realm": "me", "algorithm": "MD5"}},),
        ({"www-authenticate": 'Basic realm="me", algorithm=MD5'}, {"basic": {"realm": "me", "algorithm": "MD5"}},),
        ({"www-authenticate": 'Basic realm="me",other="fred" '}, {"basic": {"realm": "me", "other": "fred"}},),
            {"www-authenticate": 'Digest realm="digest1", qop="auth,auth-int", nonce="7102dd2", opaque="e9517f"'},
            {"digest": {"realm": "digest1", "qop": "auth,auth-int", "nonce": "7102dd2", "opaque": "e9517f"}},
        # comma between schemas (glue for multiple headers with same name)
            {"www-authenticate": 'Digest realm="2-comma-d", qop="auth-int", nonce="c0c8ff1", Basic realm="2-comma-b"'},
            {"digest": {"realm": "2-comma-d", "qop": "auth-int", "nonce": "c0c8ff1"}, "basic": {"realm": "2-comma-b"}},
        (
            {"www-authenticate": 'Digest realm="com3d", Basic realm="com3b", WSSE realm="com3w", profile="token"'},
            {"digest": {"realm": "com3d"}, "basic": {"realm": "com3b"}, "wsse": {"realm": "com3w", "profile": "token"}},
        ),
            {"www-authenticate": 'Digest realm="myrealm", nonce="KBAA=3", algorithm=MD5, qop="auth", stale=true'},
            {"digest": {"realm": "myrealm", "nonce": "KBAA=3", "algorithm": "MD5", "qop": "auth", "stale": "true"}},
        ({"www-authenticate": "Basic param='single quote'"}, {"basic": {"param": "'single"}}),
        assert httplib2.auth._parse_www_authenticate(headers) == info
@pytest.mark.parametrize(
    "data",
    (({"www-authenticate": 'OAuth "Facebook Platform" "invalid_token" "Invalid OAuth access token."'}, None),),
    ids=lambda data: str(data[0]),
)
def test_parse_www_authenticate_malformed(data):
    headers, info = data
    try:
        result = httplib2.auth._parse_www_authenticate(headers)
    except httplib2.error.MalformedHeader:
        assert info is None, "unexpected MalformedHeader"
    else:
        assert result == info
        assert info is not None, "expected parsing error"


def test_parse_www_authenticate_complexity():
    # TODO just use time.process_time() after python2 support is removed
    process_time = getattr(time, "process_time", time.time)

    def check(size):
        header = {"www-authenticate": 'scheme {0}key=value,{0}quoted="foo=bar"'.format(" \t" * size)}
        tbegin = process_time()
        result = httplib2.auth._parse_www_authenticate(header)
        tend = process_time()
        assert result == {"scheme": {"key": "value", "quoted": "foo=bar"}}
        elapsed_us = round((tend * 1e6) - (tbegin * 1e6), 0)
        return elapsed_us

    n1, n2, repeat = 50, 100, 7
    time1 = min(check(n1) for _ in range(repeat))
    time2 = min(check(n2) for _ in range(repeat))
    speed1 = round(time1 / n1, 1)
    speed2 = round(time2 / n2, 1)
    expect2 = round(speed1 * (float(n2) / n1), 1)
    error = round(speed2 / expect2, 1)
    print("x{}: time={}us speed={} us/op".format(n1, time1, speed1))
    print("x{}: time={}us speed={} us/op expected={} us/op error={}".format(n2, time2, speed2, expect2, error))
    assert error < 2, "_parse_www_authenticate scales too fast"
    response = {"www-authenticate": 'Digest realm="myrealm", nonce="KBAA=35", algorithm=MD5, qop="auth"'}
    d = httplib2.DigestAuthentication(credentials, host, request_uri, headers, response, content, None)
        'nonce="KBAA=35", uri="/test/digest/"' + ', algorithm=MD5, response="de6d4a123b80801d0e94550411b6283f", '
        "www-authenticate": 'Digest realm="myrealm", nonce="30352fd", algorithm=MD5, ' 'qop="auth", opaque="atestopaque"'
    d = httplib2.DigestAuthentication(credentials, host, request_uri, headers, response, content, None)
    response["www-authenticate"] = 'Digest realm="myrealm", nonce="bd669f", ' 'algorithm=MD5, qop="auth", stale=true'
    d = httplib2.DigestAuthentication(credentials, host, request_uri, headers, response, content, None)
    response["www-authenticate"] = 'Digest realm="myrealm", nonce="barney", ' 'algorithm=MD5, qop="auth", stale=true'
    d = httplib2.DigestAuthentication(credentials, host, request_uri, headers, response, content, None)