summaryrefslogtreecommitdiffstats
path: root/third_party/python/urllib3/dummyserver
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/python/urllib3/dummyserver
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/python/urllib3/dummyserver')
-rw-r--r--third_party/python/urllib3/dummyserver/__init__.py0
-rw-r--r--third_party/python/urllib3/dummyserver/certs/README.rst17
-rw-r--r--third_party/python/urllib3/dummyserver/certs/cacert.key27
-rw-r--r--third_party/python/urllib3/dummyserver/certs/cacert.pem21
-rw-r--r--third_party/python/urllib3/dummyserver/certs/server.crt21
-rw-r--r--third_party/python/urllib3/dummyserver/certs/server.key27
-rw-r--r--third_party/python/urllib3/dummyserver/handlers.py328
-rwxr-xr-xthird_party/python/urllib3/dummyserver/proxy.py139
-rwxr-xr-xthird_party/python/urllib3/dummyserver/server.py188
-rw-r--r--third_party/python/urllib3/dummyserver/testcase.py210
10 files changed, 978 insertions, 0 deletions
diff --git a/third_party/python/urllib3/dummyserver/__init__.py b/third_party/python/urllib3/dummyserver/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/__init__.py
diff --git a/third_party/python/urllib3/dummyserver/certs/README.rst b/third_party/python/urllib3/dummyserver/certs/README.rst
new file mode 100644
index 0000000000..7c712b6e15
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/certs/README.rst
@@ -0,0 +1,17 @@
+Generating new certificates
+---------------------------
+
+Here's how you can regenerate the certificates::
+
+ import trustme
+
+ ca = trustme.CA()
+ server_cert = ca.issue_cert(u"localhost")
+
+ ca.cert_pem.write_to_path("cacert.pem")
+ ca.private_key_pem.write_to_path("cacert.key")
+ server_cert.cert_chain_pems[0].write_to_path("server.crt")
+ server_cert.private_key_pem.write_to_path("server.key")
+
+This will break a number of tests: you will need to update the
+relevant fingerprints and hashes.
diff --git a/third_party/python/urllib3/dummyserver/certs/cacert.key b/third_party/python/urllib3/dummyserver/certs/cacert.key
new file mode 100644
index 0000000000..58e1c20167
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/certs/cacert.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAn330NEISY7w+GeZR2jh8Of1x2CtpenWRKuuD2u4FOaN0G1SN
+pm6Owum6xzhv93jmj+tZrp9kYvC9HcvGrgzH9yP566pLLfY2SEEAJYNNeVdqegY5
+9W1wa6vEDN5UYruVEbymgPHoItiYhfndgEDbagPN5dhrdNvaRNI2c5zMmBDlzzhC
+G7AZbXuthTp6OwTVye71f2lnNhKi6rzWtre/ji88fq8Qm406l29O1RTzmNttN5BZ
+nPWU9v4GnCYKXdY6BN1Ub6z8C9hna/oRgBqa0Zbv7kEuAuqhsFQeuFCfBhQm7NdP
+d/7Kh6LS+VIiCu3AOccZHOuFOjtHb/KyndmgCwIDAQABAoIBAGQg9wc308O5kmNA
+LXMKszLU4nwMBRRUaua/JPB1LeKZs3LVCnjKP+YuRox76g87X8RKxOrUNnnHGXNz
+UzBB5ehKNcS2DKy2Pi3uYOEsJZ9gOgCRmCF0q3dtRo+tpNy3V0bjYMTjGhGGWXsC
++wRhs15DNShvTkb3H3jFYFoEvo1YUKsvImBWJGwDbdDMfMZv4aeBWXlOrF+2fwt2
+TM3G1o8xzEEWBB4FLZBW+tq2zfUSa1KwqqyQ4ZIqXepjQcN6nNfuHADA+nxuruVV
+LPUhz4ZmsBEnJ7CL9zWJkLUw/al9/6Q14tleRmiZTTztqAlFgZUpNhaKSzVdsIc/
+Xz3+OgECgYEAzgNu7eFJAOzq+ZVFRrrbA7tu+f318FTCaADJ1kFgAJyj6b9yOGan
+LNL4TfXVjzgqtfQ4AIVqEHwXO7yS+YgTprvgzqRxqNRZ6ikuo2IPkIwXIAXZAlwd
+JsWLPBXOlOFW6LHvhYxjY2xF+A9y4AbuZ3UDRUQ+tp226VfEaeY80+ECgYEAxjDV
+cJqeBO06YRVGmcXfAYwRLJGT4hvIZeiSbxX/kJ0rx+cYLT/XZbAguJYQ5ZK2lkeA
+YneXYDlSTxmBxHxiWwWe3mcmctdE4Jbw8oIZ8a49a4KE/F2ojC4gmisIt3/OqGOw
+C4e/pDCE/QV64LWdazgUWHPGoVEmZx9/oMm/MWsCgYEAsLtlSJFB7ZdRpTcXLSxT
+gwoilDf36mrsNAipHjMLRrsaKwbf197If72k4kyJHspSabHO8TOC4A10aPzHIWZJ
+ZXo7y0prbyhs0mLt7Z/MNnbXx9L8bffT0lUZszwJ8tK1mf47utfK05opFDs8k0+e
+6gYJ/jwjiMoYBmoSx76KZEECgYBagJxHAmQcbdQV1yhZOhFe3H5PMt8sBnHZj32m
++o2slQkUDQRuTVPoHKikgeqPWxLDxzzqOiBHEYXzlvs6JW6okAV/G+1jzcenI2Y9
+54k/YsirWnut3nsEIGBE5lfhq5xMKtGOQlwR9xITlLgK+wQ6nO41ghD3Q15dAvY+
+D0KepwKBgQChHvbyzw0t76J2gLxUSyuG7VsId651bpqTYUsbSDFlRo4g8UbBAkHd
+fdv5BOon3ALJFreSK+a78es0kpiLwrS2SqG/y3mb9aUoLpCVB1haDmmP4Rn4AYXz
+OCfUkusuSoXOR8CMjqkXYl5QjeJAUAt9GTsZnXIbOQKbaZwkeV0HEg==
+-----END RSA PRIVATE KEY-----
diff --git a/third_party/python/urllib3/dummyserver/certs/cacert.pem b/third_party/python/urllib3/dummyserver/certs/cacert.pem
new file mode 100644
index 0000000000..710b8fbd98
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/certs/cacert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDfzCCAmegAwIBAgIUVGEi+7bkaGRIPoQrp9zFFAT5JYQwDQYJKoZIhvcNAQEL
+BQAwQDEXMBUGA1UECgwOdHJ1c3RtZSB2MC41LjMxJTAjBgNVBAsMHFRlc3Rpbmcg
+Q0EgIzdEUWJWOTBzV2xZSEstY0wwHhcNMDAwMTAxMDAwMDAwWhcNMzgwMTAxMDAw
+MDAwWjBAMRcwFQYDVQQKDA50cnVzdG1lIHYwLjUuMzElMCMGA1UECwwcVGVzdGlu
+ZyBDQSAjN0RRYlY5MHNXbFlISy1jTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAJ999DRCEmO8PhnmUdo4fDn9cdgraXp1kSrrg9ruBTmjdBtUjaZujsLp
+usc4b/d45o/rWa6fZGLwvR3Lxq4Mx/cj+euqSy32NkhBACWDTXlXanoGOfVtcGur
+xAzeVGK7lRG8poDx6CLYmIX53YBA22oDzeXYa3Tb2kTSNnOczJgQ5c84QhuwGW17
+rYU6ejsE1cnu9X9pZzYSouq81ra3v44vPH6vEJuNOpdvTtUU85jbbTeQWZz1lPb+
+BpwmCl3WOgTdVG+s/AvYZ2v6EYAamtGW7+5BLgLqobBUHrhQnwYUJuzXT3f+yoei
+0vlSIgrtwDnHGRzrhTo7R2/ysp3ZoAsCAwEAAaNxMG8wHQYDVR0OBBYEFHWf39Hn
+rdChKjsOBoBGn1U+0VgxMBIGA1UdEwEB/wQIMAYBAf8CAQkwDgYDVR0PAQH/BAQD
+AgEGMCoGA1UdJQEB/wQgMB4GCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMw
+DQYJKoZIhvcNAQELBQADggEBAIAylnWX2WTB+mrVVpE2W8i0HollTMJIPJA9Jq3Q
+/t2uPjXDEAVAcBmQju8qy2tHpamvzpQseVm3EF3UFNlGxwOGKsTzU4J45qOJITZk
+eLRAcWNEt6cgqj8ml8PuMHU7oDnp7pP6VPe5KQH1a0FYQnDNEwg7MyX+GjnXeRwd
+re6y9nMC+XKCYUAd1/nQcrZdnSsws1M5lzXir2vuyyN9EUkf2xMMKA2E1s0f+5he
+3eNghAXtZw616ITBoMb7ckG6a0+YobbiQ0tKgB8D3MG2544Gx6xhCXf7pX4q4g//
+1nTPeYFsBDyqEOEhcW1o9/MSSbjpUJC+QUmCb2Y1wYeum+w=
+-----END CERTIFICATE-----
diff --git a/third_party/python/urllib3/dummyserver/certs/server.crt b/third_party/python/urllib3/dummyserver/certs/server.crt
new file mode 100644
index 0000000000..24026c367a
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/certs/server.crt
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDeTCCAmGgAwIBAgIUQeadxkH6YMoSecB2rNbFEr1u1kUwDQYJKoZIhvcNAQEL
+BQAwQDEXMBUGA1UECgwOdHJ1c3RtZSB2MC41LjMxJTAjBgNVBAsMHFRlc3Rpbmcg
+Q0EgIzdEUWJWOTBzV2xZSEstY0wwHhcNMDAwMTAxMDAwMDAwWhcNMzgwMTAxMDAw
+MDAwWjBCMRcwFQYDVQQKDA50cnVzdG1lIHYwLjUuMzEnMCUGA1UECwweVGVzdGlu
+ZyBjZXJ0ICMtSGRsMnMyTEYyeVp0NDFOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArRLZX+5dyCh4N7q90sH2Q4Ea6QLK8OfoUQPWtpzAtINDUAdfSXCC
+/qYTtGeSCGjB4W0LfvRTI8afHoD/M+YpaCRnx7T1sy1taA2rnGrEVXEHalVP+RI4
+t4ZWtX56aez2M0Fs6o4MtzAuP6fKgSdWzIvOmtCxqn0Zf2KbfEHnQylsy2LgPa/x
+Lg50fbZ195+h4EAB3d2/jqaeFGGhN+7zrrv4+L1eeW3bzOkvPEkTNepq3Gy/8r5e
+0i2icEnM+eBfl8NYgQ1toJYvDIy5Qi2TRzaFxBVmqUOc8EFtHpL7E9YLbTTW15xd
+oLVLdXI5igGxkwPYoeiiAJWxIsC/hL1RRQIDAQABo2kwZzAdBgNVHQ4EFgQUMU6+
+uwNmL8TxLwjrj7jzzlwDPiowDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBR1n9/R
+563QoSo7DgaARp9VPtFYMTAXBgNVHREBAf8EDTALgglsb2NhbGhvc3QwDQYJKoZI
+hvcNAQELBQADggEBAJ6w5neQKw+/efA/I3IHzt8GaSHQ/YehMHx8GxCViJUmLg6P
+Vf74k856Knvh7IsVaqF1uRi6qQaFPik6CwtBCj7/ZftdseOCDljd+8EWyQ+ZWie7
++tzMIdQWZxYSdR9Ov42VD++a6oWJtfJhWV5eyDit99FFK31/M1ZXoceiDS5AsIG6
+wfsxrFj1qV9pLNSIlfrnycYhYx7avVJTf+2mfZgTO9Tx+VPapkZrfCnP/2jpN39u
+zblFFjP9Ir0QqBw7MXjVX+Y1HkQ2TQnEeSsp1HuFRIZYx72Cttnckv1Lxcx/HiQB
+oebTDYiRfxOAEeIMgIhX88Jca8vNIRcXDeGK9mU=
+-----END CERTIFICATE-----
diff --git a/third_party/python/urllib3/dummyserver/certs/server.key b/third_party/python/urllib3/dummyserver/certs/server.key
new file mode 100644
index 0000000000..592ee6bea2
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/certs/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEArRLZX+5dyCh4N7q90sH2Q4Ea6QLK8OfoUQPWtpzAtINDUAdf
+SXCC/qYTtGeSCGjB4W0LfvRTI8afHoD/M+YpaCRnx7T1sy1taA2rnGrEVXEHalVP
++RI4t4ZWtX56aez2M0Fs6o4MtzAuP6fKgSdWzIvOmtCxqn0Zf2KbfEHnQylsy2Lg
+Pa/xLg50fbZ195+h4EAB3d2/jqaeFGGhN+7zrrv4+L1eeW3bzOkvPEkTNepq3Gy/
+8r5e0i2icEnM+eBfl8NYgQ1toJYvDIy5Qi2TRzaFxBVmqUOc8EFtHpL7E9YLbTTW
+15xdoLVLdXI5igGxkwPYoeiiAJWxIsC/hL1RRQIDAQABAoIBAQCZ/62f6G9WDHx7
+yhPhlmjTw+r37l45YYCbpbjFoFDvzeR1LzogFJbak1fxLD8KcHwjY23ZNvlLWg53
+i/yIZ4Hsgog9cM0283LoJVHPykiMZhhdCzAvxYDl/AjnUXUHD6w6CzsoseCql5pv
+VZOgvCpFsxjRNGUB+HJZoJoNRG7MmHaY638pGHGMiVbskT9Ww3emtMLdTKy1rQcj
+9XO/4mlaBGD79wYxy5Hlysbh2UYuQRv8XN5V46Uugk6sC/i2G7VC8KkqPQ2pM+rA
+LaeWSuN9dfBwiKcHtJssMP95ilsXsjqh3NoVuFODDXHv3u+nBAxtg2TnLZFkDZXH
+FvxPJu8BAoGBANwWWzvl/dnTtVDFbhF/61v3AVZV4vVpokXRJKny05CZPZWROTc4
+LXMVw9kxeecNdo0yR4jn1yyNUmKnQpTmpsR9Yo9NYH+Z1CLxswpc7ILfVRZBK6bK
+cCG43lM5xZprG6FXhqkHN2u9z5Y8/PuaMzC8iVs402/gakgPKmn8OjdhAoGBAMlQ
+mmrx24n9YY/dOn55XC5V/iN3Z6mIsHThnDIU515bwLwZVG7chOLSiWHAh4JzUH+v
+bV3NnlE1jhf5ln0WAadCtIeVprJG6msNTQlbTMTTV5kVNdbfYX6sFQEI+hC1LCiV
+yJtuNIa4P5W/PtoC3FWUlcAH8C91S/M4CeZZ0HhlAoGBAIxflgE2SBrO9S53PiTb
+OfqGKMwwK3nrzhxJsODUiCwKEUV8Qsn9gr+MekXlUKMV6y9Tily/wnYgDRPvKoBe
+PK/GaT6NU6cPLka7cj6B1jgCyfpPxs+y/qIDj4n1pxs+hXj6omDcwXRutCBW9eRk
+DZJgLhuIuxL4R9F+GsdOoLMBAoGAKQn1cLe9OXQd32YJ9p5m3EtLc49z4murDSiw
+3sTEJcgukinXvIHX1SV2PCczeLRpRJ5OfUDddVCllt2agAVscNx4UOuA//bU8t3T
+RoUGMVmkEeDxCMyg42HRJlTeJWnJhryCGK1up8gHrk8+UNMkd43CuVLk88fFo99Y
+pUzJ4sECgYEAvBDTo3k3sD18qV6p6tQwy+MVjvQb9V81GHP18tYcVKta3LkkqUFa
+3qSyVxi7gl3JtynG7NJ7+GDx6zxW2xUR72NTcJwWvesLI+1orM288pyNDVw9MJ/j
+AyVFnW5SEYEqdizTnQxL+rQB4CyeHfwZx2/1/Qr0ezLGUJv51lnk4mQ=
+-----END RSA PRIVATE KEY-----
diff --git a/third_party/python/urllib3/dummyserver/handlers.py b/third_party/python/urllib3/dummyserver/handlers.py
new file mode 100644
index 0000000000..696dbab076
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/handlers.py
@@ -0,0 +1,328 @@
+from __future__ import print_function
+
+import collections
+import contextlib
+import gzip
+import json
+import logging
+import sys
+import time
+import zlib
+
+from io import BytesIO
+from tornado.web import RequestHandler
+from tornado import httputil
+from datetime import datetime
+from datetime import timedelta
+
+from urllib3.packages.six.moves.http_client import responses
+from urllib3.packages.six.moves.urllib.parse import urlsplit
+from urllib3.packages.six import binary_type, ensure_str
+
+log = logging.getLogger(__name__)
+
+
+class Response(object):
+ def __init__(self, body="", status="200 OK", headers=None):
+ self.body = body
+ self.status = status
+ self.headers = headers or [("Content-type", "text/plain")]
+
+ def __call__(self, request_handler):
+ status, reason = self.status.split(" ", 1)
+ request_handler.set_status(int(status), reason)
+ for header, value in self.headers:
+ request_handler.add_header(header, value)
+
+ # chunked
+ if isinstance(self.body, list):
+ for item in self.body:
+ if not isinstance(item, bytes):
+ item = item.encode("utf8")
+ request_handler.write(item)
+ request_handler.flush()
+ else:
+ body = self.body
+ if not isinstance(body, bytes):
+ body = body.encode("utf8")
+
+ request_handler.write(body)
+
+
+RETRY_TEST_NAMES = collections.defaultdict(int)
+
+
+class TestingApp(RequestHandler):
+ """
+ Simple app that performs various operations, useful for testing an HTTP
+ library.
+
+ Given any path, it will attempt to load a corresponding local method if
+ it exists. Status code 200 indicates success, 400 indicates failure. Each
+ method has its own conditions for success/failure.
+ """
+
+ def get(self):
+ """ Handle GET requests """
+ self._call_method()
+
+ def post(self):
+ """ Handle POST requests """
+ self._call_method()
+
+ def put(self):
+ """ Handle PUT requests """
+ self._call_method()
+
+ def options(self):
+ """ Handle OPTIONS requests """
+ self._call_method()
+
+ def head(self):
+ """ Handle HEAD requests """
+ self._call_method()
+
+ def _call_method(self):
+ """ Call the correct method in this class based on the incoming URI """
+ req = self.request
+ req.params = {}
+ for k, v in req.arguments.items():
+ req.params[k] = next(iter(v))
+
+ path = req.path[:]
+ if not path.startswith("/"):
+ path = urlsplit(path).path
+
+ target = path[1:].split("/", 1)[0]
+ method = getattr(self, target, self.index)
+
+ resp = method(req)
+
+ if dict(resp.headers).get("Connection") == "close":
+ # FIXME: Can we kill the connection somehow?
+ pass
+
+ resp(self)
+
+ def index(self, _request):
+ "Render simple message"
+ return Response("Dummy server!")
+
+ def certificate(self, request):
+ """Return the requester's certificate."""
+ cert = request.get_ssl_certificate()
+ subject = dict()
+ if cert is not None:
+ subject = dict((k, v) for (k, v) in [y for z in cert["subject"] for y in z])
+ return Response(json.dumps(subject))
+
+ def source_address(self, request):
+ """Return the requester's IP address."""
+ return Response(request.remote_ip)
+
+ def set_up(self, request):
+ test_type = request.params.get("test_type")
+ test_id = request.params.get("test_id")
+ if test_id:
+ print("\nNew test %s: %s" % (test_type, test_id))
+ else:
+ print("\nNew test %s" % test_type)
+ return Response("Dummy server is ready!")
+
+ def specific_method(self, request):
+ "Confirm that the request matches the desired method type"
+ method = request.params.get("method")
+ if method and not isinstance(method, str):
+ method = method.decode("utf8")
+
+ if request.method != method:
+ return Response(
+ "Wrong method: %s != %s" % (method, request.method),
+ status="400 Bad Request",
+ )
+ return Response()
+
+ def upload(self, request):
+ "Confirm that the uploaded file conforms to specification"
+ # FIXME: This is a huge broken mess
+ param = request.params.get("upload_param", b"myfile").decode("ascii")
+ filename = request.params.get("upload_filename", b"").decode("utf-8")
+ size = int(request.params.get("upload_size", "0"))
+ files_ = request.files.get(param)
+
+ if len(files_) != 1:
+ return Response(
+ "Expected 1 file for '%s', not %d" % (param, len(files_)),
+ status="400 Bad Request",
+ )
+ file_ = files_[0]
+
+ data = file_["body"]
+ if int(size) != len(data):
+ return Response(
+ "Wrong size: %d != %d" % (size, len(data)), status="400 Bad Request"
+ )
+
+ got_filename = file_["filename"]
+ if isinstance(got_filename, binary_type):
+ got_filename = got_filename.decode("utf-8")
+
+ # Tornado can leave the trailing \n in place on the filename.
+ if filename != got_filename:
+ return Response(
+ u"Wrong filename: %s != %s" % (filename, file_.filename),
+ status="400 Bad Request",
+ )
+
+ return Response()
+
+ def redirect(self, request):
+ "Perform a redirect to ``target``"
+ target = request.params.get("target", "/")
+ status = request.params.get("status", "303 See Other")
+ if len(status) == 3:
+ status = "%s Redirect" % status.decode("latin-1")
+
+ headers = [("Location", target)]
+ return Response(status=status, headers=headers)
+
+ def not_found(self, request):
+ return Response("Not found", status="404 Not Found")
+
+ def multi_redirect(self, request):
+ "Performs a redirect chain based on ``redirect_codes``"
+ codes = request.params.get("redirect_codes", b"200").decode("utf-8")
+ head, tail = codes.split(",", 1) if "," in codes else (codes, None)
+ status = "{0} {1}".format(head, responses[int(head)])
+ if not tail:
+ return Response("Done redirecting", status=status)
+
+ headers = [("Location", "/multi_redirect?redirect_codes=%s" % tail)]
+ return Response(status=status, headers=headers)
+
+ def keepalive(self, request):
+ if request.params.get("close", b"0") == b"1":
+ headers = [("Connection", "close")]
+ return Response("Closing", headers=headers)
+
+ headers = [("Connection", "keep-alive")]
+ return Response("Keeping alive", headers=headers)
+
+ def echo_params(self, request):
+ params = sorted(
+ [(ensure_str(k), ensure_str(v)) for k, v in request.params.items()]
+ )
+ return Response(repr(params))
+
+ def sleep(self, request):
+ "Sleep for a specified amount of ``seconds``"
+ # DO NOT USE THIS, IT'S DEPRECATED.
+ # FIXME: Delete this once appengine tests are fixed to not use this handler.
+ seconds = float(request.params.get("seconds", "1"))
+ time.sleep(seconds)
+ return Response()
+
+ def echo(self, request):
+ "Echo back the params"
+ if request.method == "GET":
+ return Response(request.query)
+
+ return Response(request.body)
+
+ def echo_uri(self, request):
+ "Echo back the requested URI"
+ return Response(request.uri)
+
+ def encodingrequest(self, request):
+ "Check for UA accepting gzip/deflate encoding"
+ data = b"hello, world!"
+ encoding = request.headers.get("Accept-Encoding", "")
+ headers = None
+ if encoding == "gzip":
+ headers = [("Content-Encoding", "gzip")]
+ file_ = BytesIO()
+ with contextlib.closing(
+ gzip.GzipFile("", mode="w", fileobj=file_)
+ ) as zipfile:
+ zipfile.write(data)
+ data = file_.getvalue()
+ elif encoding == "deflate":
+ headers = [("Content-Encoding", "deflate")]
+ data = zlib.compress(data)
+ elif encoding == "garbage-gzip":
+ headers = [("Content-Encoding", "gzip")]
+ data = "garbage"
+ elif encoding == "garbage-deflate":
+ headers = [("Content-Encoding", "deflate")]
+ data = "garbage"
+ return Response(data, headers=headers)
+
+ def headers(self, request):
+ return Response(json.dumps(dict(request.headers)))
+
+ def successful_retry(self, request):
+ """ Handler which will return an error and then success
+
+ It's not currently very flexible as the number of retries is hard-coded.
+ """
+ test_name = request.headers.get("test-name", None)
+ if not test_name:
+ return Response("test-name header not set", status="400 Bad Request")
+
+ RETRY_TEST_NAMES[test_name] += 1
+
+ if RETRY_TEST_NAMES[test_name] >= 2:
+ return Response("Retry successful!")
+ else:
+ return Response("need to keep retrying!", status="418 I'm A Teapot")
+
+ def chunked(self, request):
+ return Response(["123"] * 4)
+
+ def chunked_gzip(self, request):
+ chunks = []
+ compressor = zlib.compressobj(6, zlib.DEFLATED, 16 + zlib.MAX_WBITS)
+
+ for uncompressed in [b"123"] * 4:
+ chunks.append(compressor.compress(uncompressed))
+
+ chunks.append(compressor.flush())
+
+ return Response(chunks, headers=[("Content-Encoding", "gzip")])
+
+ def nbytes(self, request):
+ length = int(request.params.get("length"))
+ data = b"1" * length
+ return Response(data, headers=[("Content-Type", "application/octet-stream")])
+
+ def status(self, request):
+ status = request.params.get("status", "200 OK")
+
+ return Response(status=status)
+
+ def retry_after(self, request):
+ if datetime.now() - self.application.last_req < timedelta(seconds=1):
+ status = request.params.get("status", b"429 Too Many Requests")
+ return Response(
+ status=status.decode("utf-8"), headers=[("Retry-After", "1")]
+ )
+
+ self.application.last_req = datetime.now()
+
+ return Response(status="200 OK")
+
+ def redirect_after(self, request):
+ "Perform a redirect to ``target``"
+ date = request.params.get("date")
+ if date:
+ retry_after = str(
+ httputil.format_timestamp(datetime.fromtimestamp(float(date)))
+ )
+ else:
+ retry_after = "1"
+ target = request.params.get("target", "/")
+ headers = [("Location", target), ("Retry-After", retry_after)]
+ return Response(status="303 See Other", headers=headers)
+
+ def shutdown(self, request):
+ sys.exit()
diff --git a/third_party/python/urllib3/dummyserver/proxy.py b/third_party/python/urllib3/dummyserver/proxy.py
new file mode 100755
index 0000000000..c4f0b824f7
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/proxy.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+#
+# Simple asynchronous HTTP proxy with tunnelling (CONNECT).
+#
+# GET/POST proxying based on
+# http://groups.google.com/group/python-tornado/msg/7bea08e7a049cf26
+#
+# Copyright (C) 2012 Senko Rasic <senko.rasic@dobarkod.hr>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import sys
+import socket
+
+import tornado.gen
+import tornado.httpserver
+import tornado.ioloop
+import tornado.iostream
+import tornado.web
+import tornado.httpclient
+
+__all__ = ["ProxyHandler", "run_proxy"]
+
+
+class ProxyHandler(tornado.web.RequestHandler):
+ SUPPORTED_METHODS = ["GET", "POST", "CONNECT"]
+
+ @tornado.gen.coroutine
+ def get(self):
+ def handle_response(response):
+ if response.error and not isinstance(
+ response.error, tornado.httpclient.HTTPError
+ ):
+ self.set_status(500)
+ self.write("Internal server error:\n" + str(response.error))
+ self.finish()
+ else:
+ self.set_status(response.code)
+ for header in (
+ "Date",
+ "Cache-Control",
+ "Server",
+ "Content-Type",
+ "Location",
+ ):
+ v = response.headers.get(header)
+ if v:
+ self.set_header(header, v)
+ if response.body:
+ self.write(response.body)
+ self.finish()
+
+ req = tornado.httpclient.HTTPRequest(
+ url=self.request.uri,
+ method=self.request.method,
+ body=self.request.body,
+ headers=self.request.headers,
+ follow_redirects=False,
+ allow_nonstandard_methods=True,
+ )
+
+ client = tornado.httpclient.AsyncHTTPClient()
+ try:
+ response = yield client.fetch(req)
+ yield handle_response(response)
+ except tornado.httpclient.HTTPError as e:
+ if hasattr(e, "response") and e.response:
+ yield handle_response(e.response)
+ else:
+ self.set_status(500)
+ self.write("Internal server error:\n" + str(e))
+ self.finish()
+
+ @tornado.gen.coroutine
+ def post(self):
+ yield self.get()
+
+ @tornado.gen.coroutine
+ def connect(self):
+ host, port = self.request.uri.split(":")
+ client = self.request.connection.stream
+
+ @tornado.gen.coroutine
+ def start_forward(reader, writer):
+ while True:
+ try:
+ data = yield reader.read_bytes(4096, partial=True)
+ except tornado.iostream.StreamClosedError:
+ break
+ if not data:
+ break
+ writer.write(data)
+ writer.close()
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ upstream = tornado.iostream.IOStream(s)
+ yield upstream.connect((host, int(port)))
+
+ client.write(b"HTTP/1.0 200 Connection established\r\n\r\n")
+ fu1 = start_forward(client, upstream)
+ fu2 = start_forward(upstream, client)
+ yield [fu1, fu2]
+
+
+def run_proxy(port, start_ioloop=True):
+ """
+ Run proxy on the specified port. If start_ioloop is True (default),
+ the tornado IOLoop will be started immediately.
+ """
+ app = tornado.web.Application([(r".*", ProxyHandler)])
+ app.listen(port)
+ ioloop = tornado.ioloop.IOLoop.instance()
+ if start_ioloop:
+ ioloop.start()
+
+
+if __name__ == "__main__":
+ port = 8888
+ if len(sys.argv) > 1:
+ port = int(sys.argv[1])
+
+ print("Starting HTTP proxy on port %d" % port)
+ run_proxy(port)
diff --git a/third_party/python/urllib3/dummyserver/server.py b/third_party/python/urllib3/dummyserver/server.py
new file mode 100755
index 0000000000..68f383534e
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/server.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+
+"""
+Dummy server used for unit testing.
+"""
+from __future__ import print_function
+
+import logging
+import os
+import sys
+import threading
+import socket
+import warnings
+import ssl
+from datetime import datetime
+
+from urllib3.exceptions import HTTPWarning
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+import tornado.httpserver
+import tornado.ioloop
+import tornado.netutil
+import tornado.web
+import trustme
+
+
+log = logging.getLogger(__name__)
+
+CERTS_PATH = os.path.join(os.path.dirname(__file__), "certs")
+DEFAULT_CERTS = {
+ "certfile": os.path.join(CERTS_PATH, "server.crt"),
+ "keyfile": os.path.join(CERTS_PATH, "server.key"),
+ "cert_reqs": ssl.CERT_OPTIONAL,
+ "ca_certs": os.path.join(CERTS_PATH, "cacert.pem"),
+}
+DEFAULT_CA = os.path.join(CERTS_PATH, "cacert.pem")
+DEFAULT_CA_KEY = os.path.join(CERTS_PATH, "cacert.key")
+
+
+def _resolves_to_ipv6(host):
+ """ Returns True if the system resolves host to an IPv6 address by default. """
+ resolves_to_ipv6 = False
+ try:
+ for res in socket.getaddrinfo(host, None, socket.AF_UNSPEC):
+ af, _, _, _, _ = res
+ if af == socket.AF_INET6:
+ resolves_to_ipv6 = True
+ except socket.gaierror:
+ pass
+
+ return resolves_to_ipv6
+
+
+def _has_ipv6(host):
+ """ Returns True if the system can bind an IPv6 address. """
+ sock = None
+ has_ipv6 = False
+
+ if socket.has_ipv6:
+ # has_ipv6 returns true if cPython was compiled with IPv6 support.
+ # It does not tell us if the system has IPv6 support enabled. To
+ # determine that we must bind to an IPv6 address.
+ # https://github.com/urllib3/urllib3/pull/611
+ # https://bugs.python.org/issue658327
+ try:
+ sock = socket.socket(socket.AF_INET6)
+ sock.bind((host, 0))
+ has_ipv6 = _resolves_to_ipv6("localhost")
+ except Exception:
+ pass
+
+ if sock:
+ sock.close()
+ return has_ipv6
+
+
+# Some systems may have IPv6 support but DNS may not be configured
+# properly. We can not count that localhost will resolve to ::1 on all
+# systems. See https://github.com/urllib3/urllib3/pull/611 and
+# https://bugs.python.org/issue18792
+HAS_IPV6_AND_DNS = _has_ipv6("localhost")
+HAS_IPV6 = _has_ipv6("::1")
+
+
+# Different types of servers we have:
+
+
+class NoIPv6Warning(HTTPWarning):
+ "IPv6 is not available"
+ pass
+
+
+class SocketServerThread(threading.Thread):
+ """
+ :param socket_handler: Callable which receives a socket argument for one
+ request.
+ :param ready_event: Event which gets set when the socket handler is
+ ready to receive requests.
+ """
+
+ USE_IPV6 = HAS_IPV6_AND_DNS
+
+ def __init__(self, socket_handler, host="localhost", port=8081, ready_event=None):
+ threading.Thread.__init__(self)
+ self.daemon = True
+
+ self.socket_handler = socket_handler
+ self.host = host
+ self.ready_event = ready_event
+
+ def _start_server(self):
+ if self.USE_IPV6:
+ sock = socket.socket(socket.AF_INET6)
+ else:
+ warnings.warn("No IPv6 support. Falling back to IPv4.", NoIPv6Warning)
+ sock = socket.socket(socket.AF_INET)
+ if sys.platform != "win32":
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind((self.host, 0))
+ self.port = sock.getsockname()[1]
+
+ # Once listen() returns, the server socket is ready
+ sock.listen(1)
+
+ if self.ready_event:
+ self.ready_event.set()
+
+ self.socket_handler(sock)
+ sock.close()
+
+ def run(self):
+ self.server = self._start_server()
+
+
+def run_tornado_app(app, io_loop, certs, scheme, host):
+ assert io_loop == tornado.ioloop.IOLoop.current()
+
+ # We can't use fromtimestamp(0) because of CPython issue 29097, so we'll
+ # just construct the datetime object directly.
+ app.last_req = datetime(1970, 1, 1)
+
+ if scheme == "https":
+ http_server = tornado.httpserver.HTTPServer(app, ssl_options=certs)
+ else:
+ http_server = tornado.httpserver.HTTPServer(app)
+
+ sockets = tornado.netutil.bind_sockets(None, address=host)
+ port = sockets[0].getsockname()[1]
+ http_server.add_sockets(sockets)
+ return http_server, port
+
+
+def run_loop_in_thread(io_loop):
+ t = threading.Thread(target=io_loop.start)
+ t.start()
+ return t
+
+
+def get_unreachable_address():
+ # reserved as per rfc2606
+ return ("something.invalid", 54321)
+
+
+if __name__ == "__main__":
+ # For debugging dummyserver itself - python -m dummyserver.server
+ from .testcase import TestingApp
+
+ host = "127.0.0.1"
+
+ io_loop = tornado.ioloop.IOLoop.current()
+ app = tornado.web.Application([(r".*", TestingApp)])
+ server, port = run_tornado_app(app, io_loop, None, "http", host)
+ server_thread = run_loop_in_thread(io_loop)
+
+ print("Listening on http://{host}:{port}".format(host=host, port=port))
+
+
+def encrypt_key_pem(private_key_pem, password):
+ private_key = serialization.load_pem_private_key(
+ private_key_pem.bytes(), password=None, backend=default_backend()
+ )
+ encrypted_key = private_key.private_bytes(
+ serialization.Encoding.PEM,
+ serialization.PrivateFormat.TraditionalOpenSSL,
+ serialization.BestAvailableEncryption(password),
+ )
+ return trustme.Blob(encrypted_key)
diff --git a/third_party/python/urllib3/dummyserver/testcase.py b/third_party/python/urllib3/dummyserver/testcase.py
new file mode 100644
index 0000000000..90c6b2240c
--- /dev/null
+++ b/third_party/python/urllib3/dummyserver/testcase.py
@@ -0,0 +1,210 @@
+import threading
+
+import pytest
+from tornado import ioloop, web
+
+from dummyserver.server import (
+ SocketServerThread,
+ run_tornado_app,
+ run_loop_in_thread,
+ DEFAULT_CERTS,
+ HAS_IPV6,
+)
+from dummyserver.handlers import TestingApp
+from dummyserver.proxy import ProxyHandler
+
+
+def consume_socket(sock, chunks=65536):
+ consumed = bytearray()
+ while True:
+ b = sock.recv(chunks)
+ consumed += b
+ if b.endswith(b"\r\n\r\n"):
+ break
+ return consumed
+
+
+class SocketDummyServerTestCase(object):
+ """
+ A simple socket-based server is created for this class that is good for
+ exactly one request.
+ """
+
+ scheme = "http"
+ host = "localhost"
+
+ @classmethod
+ def _start_server(cls, socket_handler):
+ ready_event = threading.Event()
+ cls.server_thread = SocketServerThread(
+ socket_handler=socket_handler, ready_event=ready_event, host=cls.host
+ )
+ cls.server_thread.start()
+ ready_event.wait(5)
+ if not ready_event.is_set():
+ raise Exception("most likely failed to start server")
+ cls.port = cls.server_thread.port
+
+ @classmethod
+ def start_response_handler(cls, response, num=1, block_send=None):
+ ready_event = threading.Event()
+
+ def socket_handler(listener):
+ for _ in range(num):
+ ready_event.set()
+
+ sock = listener.accept()[0]
+ consume_socket(sock)
+ if block_send:
+ block_send.wait()
+ block_send.clear()
+ sock.send(response)
+ sock.close()
+
+ cls._start_server(socket_handler)
+ return ready_event
+
+ @classmethod
+ def start_basic_handler(cls, **kw):
+ return cls.start_response_handler(
+ b"HTTP/1.1 200 OK\r\n" b"Content-Length: 0\r\n" b"\r\n", **kw
+ )
+
+ @classmethod
+ def teardown_class(cls):
+ if hasattr(cls, "server_thread"):
+ cls.server_thread.join(0.1)
+
+ def assert_header_received(
+ self, received_headers, header_name, expected_value=None
+ ):
+ header_name = header_name.encode("ascii")
+ if expected_value is not None:
+ expected_value = expected_value.encode("ascii")
+ header_titles = []
+ for header in received_headers:
+ key, value = header.split(b": ")
+ header_titles.append(key)
+ if key == header_name and expected_value is not None:
+ assert value == expected_value
+ assert header_name in header_titles
+
+
+class IPV4SocketDummyServerTestCase(SocketDummyServerTestCase):
+ @classmethod
+ def _start_server(cls, socket_handler):
+ ready_event = threading.Event()
+ cls.server_thread = SocketServerThread(
+ socket_handler=socket_handler, ready_event=ready_event, host=cls.host
+ )
+ cls.server_thread.USE_IPV6 = False
+ cls.server_thread.start()
+ ready_event.wait(5)
+ if not ready_event.is_set():
+ raise Exception("most likely failed to start server")
+ cls.port = cls.server_thread.port
+
+
+class HTTPDummyServerTestCase(object):
+ """ A simple HTTP server that runs when your test class runs
+
+ Have your test class inherit from this one, and then a simple server
+ will start when your tests run, and automatically shut down when they
+ complete. For examples of what test requests you can send to the server,
+ see the TestingApp in dummyserver/handlers.py.
+ """
+
+ scheme = "http"
+ host = "localhost"
+ host_alt = "127.0.0.1" # Some tests need two hosts
+ certs = DEFAULT_CERTS
+
+ @classmethod
+ def _start_server(cls):
+ cls.io_loop = ioloop.IOLoop.current()
+ app = web.Application([(r".*", TestingApp)])
+ cls.server, cls.port = run_tornado_app(
+ app, cls.io_loop, cls.certs, cls.scheme, cls.host
+ )
+ cls.server_thread = run_loop_in_thread(cls.io_loop)
+
+ @classmethod
+ def _stop_server(cls):
+ cls.io_loop.add_callback(cls.server.stop)
+ cls.io_loop.add_callback(cls.io_loop.stop)
+ cls.server_thread.join()
+
+ @classmethod
+ def setup_class(cls):
+ cls._start_server()
+
+ @classmethod
+ def teardown_class(cls):
+ cls._stop_server()
+
+
+class HTTPSDummyServerTestCase(HTTPDummyServerTestCase):
+ scheme = "https"
+ host = "localhost"
+ certs = DEFAULT_CERTS
+
+
+class HTTPDummyProxyTestCase(object):
+
+ http_host = "localhost"
+ http_host_alt = "127.0.0.1"
+
+ https_host = "localhost"
+ https_host_alt = "127.0.0.1"
+ https_certs = DEFAULT_CERTS
+
+ proxy_host = "localhost"
+ proxy_host_alt = "127.0.0.1"
+
+ @classmethod
+ def setup_class(cls):
+ cls.io_loop = ioloop.IOLoop.current()
+
+ app = web.Application([(r".*", TestingApp)])
+ cls.http_server, cls.http_port = run_tornado_app(
+ app, cls.io_loop, None, "http", cls.http_host
+ )
+
+ app = web.Application([(r".*", TestingApp)])
+ cls.https_server, cls.https_port = run_tornado_app(
+ app, cls.io_loop, cls.https_certs, "https", cls.http_host
+ )
+
+ app = web.Application([(r".*", ProxyHandler)])
+ cls.proxy_server, cls.proxy_port = run_tornado_app(
+ app, cls.io_loop, None, "http", cls.proxy_host
+ )
+
+ cls.server_thread = run_loop_in_thread(cls.io_loop)
+
+ @classmethod
+ def teardown_class(cls):
+ cls.io_loop.add_callback(cls.http_server.stop)
+ cls.io_loop.add_callback(cls.https_server.stop)
+ cls.io_loop.add_callback(cls.proxy_server.stop)
+ cls.io_loop.add_callback(cls.io_loop.stop)
+ cls.server_thread.join()
+
+
+@pytest.mark.skipif(not HAS_IPV6, reason="IPv6 not available")
+class IPv6HTTPDummyServerTestCase(HTTPDummyServerTestCase):
+ host = "::1"
+
+
+@pytest.mark.skipif(not HAS_IPV6, reason="IPv6 not available")
+class IPv6HTTPDummyProxyTestCase(HTTPDummyProxyTestCase):
+
+ http_host = "localhost"
+ http_host_alt = "127.0.0.1"
+
+ https_host = "localhost"
+ https_host_alt = "127.0.0.1"
+ https_certs = DEFAULT_CERTS
+
+ proxy_host = "::1"
+ proxy_host_alt = "127.0.0.1"