diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/__init__.py | 9 | ||||
-rw-r--r-- | test/mitmproxy/__init__.py | 9 | ||||
-rw-r--r-- | test/mitmproxy/completion/aaa (renamed from test/completion/aaa) | 0 | ||||
-rw-r--r-- | test/mitmproxy/completion/aab (renamed from test/completion/aab) | 0 | ||||
-rw-r--r-- | test/mitmproxy/completion/aac (renamed from test/completion/aac) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/1.css (renamed from test/data/1.css) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/amf01 (renamed from test/data/amf01) | bin | 432 -> 432 bytes | |||
-rw-r--r-- | test/mitmproxy/data/amf02 (renamed from test/data/amf02) | bin | 286 -> 286 bytes | |||
-rw-r--r-- | test/mitmproxy/data/amf03 (renamed from test/data/amf03) | bin | 33691 -> 33691 bytes | |||
-rw-r--r-- | test/mitmproxy/data/clientcert/.gitignore (renamed from test/data/clientcert/.gitignore) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/clientcert/127.0.0.1.pem (renamed from test/data/clientcert/127.0.0.1.pem) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/clientcert/client.cnf (renamed from test/data/clientcert/client.cnf) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/clientcert/client.pem (renamed from test/data/clientcert/client.pem) | 0 | ||||
-rw-r--r--[-rwxr-xr-x] | test/mitmproxy/data/clientcert/make (renamed from test/data/clientcert/make) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/confdir/mitmproxy-ca-cert.cer (renamed from test/data/confdir/mitmproxy-ca-cert.cer) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/confdir/mitmproxy-ca-cert.p12 (renamed from test/data/confdir/mitmproxy-ca-cert.p12) | bin | 1689 -> 1689 bytes | |||
-rw-r--r-- | test/mitmproxy/data/confdir/mitmproxy-ca-cert.pem (renamed from test/data/confdir/mitmproxy-ca-cert.pem) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/confdir/mitmproxy-ca.pem (renamed from test/data/confdir/mitmproxy-ca.pem) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/dercert (renamed from test/data/dercert) | bin | 1838 -> 1838 bytes | |||
-rw-r--r-- | test/mitmproxy/data/dumpfile-012 (renamed from test/data/dumpfile-012) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/dumpfile-013 (renamed from test/data/dumpfile-013) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/htpasswd (renamed from test/data/htpasswd) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/htpasswd.invalid (renamed from test/data/htpasswd.invalid) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/image-err1.jpg (renamed from test/data/image-err1.jpg) | bin | 82674 -> 82674 bytes | |||
-rw-r--r-- | test/mitmproxy/data/image.gif (renamed from test/data/image.gif) | bin | 2398 -> 2398 bytes | |||
-rw-r--r-- | test/mitmproxy/data/image.ico (renamed from test/data/image.ico) | bin | 11502 -> 11502 bytes | |||
-rw-r--r-- | test/mitmproxy/data/image.jpg (renamed from test/data/image.jpg) | bin | 1568 -> 1568 bytes | |||
-rw-r--r-- | test/mitmproxy/data/image.png (renamed from test/data/image.png) | bin | 9311 -> 9311 bytes | |||
-rw-r--r-- | test/mitmproxy/data/no_common_name.pem (renamed from test/data/no_common_name.pem) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/pf01 (renamed from test/data/pf01) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/pf02 (renamed from test/data/pf02) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/protobuf01 (renamed from test/data/protobuf01) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/replace (renamed from test/data/replace) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/testkey.pem (renamed from test/data/testkey.pem) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/trusted-cadir/8117bdb9.0 (renamed from test/data/trusted-cadir/8117bdb9.0) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/trusted-cadir/9d45e6a9.0 (renamed from test/data/trusted-cadir/9d45e6a9.0) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/trusted-cadir/trusted-ca.pem (renamed from test/data/trusted-cadir/trusted-ca.pem) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/trusted-server.crt (renamed from test/data/trusted-server.crt) | 0 | ||||
-rw-r--r-- | test/mitmproxy/data/untrusted-server.crt (renamed from test/data/untrusted-server.crt) | 0 | ||||
-rw-r--r-- | test/mitmproxy/fuzzing/.env (renamed from test/fuzzing/.env) | 0 | ||||
-rw-r--r-- | test/mitmproxy/fuzzing/README (renamed from test/fuzzing/README) | 0 | ||||
-rw-r--r-- | test/mitmproxy/fuzzing/client_patterns (renamed from test/fuzzing/client_patterns) | 0 | ||||
-rw-r--r--[-rwxr-xr-x] | test/mitmproxy/fuzzing/go_proxy (renamed from test/fuzzing/go_proxy) | 0 | ||||
-rw-r--r-- | test/mitmproxy/fuzzing/reverse_patterns (renamed from test/fuzzing/reverse_patterns) | 0 | ||||
-rw-r--r-- | test/mitmproxy/fuzzing/straight_stream (renamed from test/fuzzing/straight_stream) | 0 | ||||
-rw-r--r-- | test/mitmproxy/fuzzing/straight_stream_patterns (renamed from test/fuzzing/straight_stream_patterns) | 0 | ||||
-rw-r--r-- | test/mitmproxy/fuzzing/straight_stream_ssl (renamed from test/fuzzing/straight_stream_ssl) | 0 | ||||
-rw-r--r-- | test/mitmproxy/mock_urwid.py (renamed from test/mock_urwid.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/a.py (renamed from test/scripts/a.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/a_helper.py (renamed from test/scripts/a_helper.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/all.py (renamed from test/scripts/all.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/concurrent_decorator.py (renamed from test/scripts/concurrent_decorator.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/concurrent_decorator_err.py (renamed from test/scripts/concurrent_decorator_err.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/duplicate_flow.py (renamed from test/scripts/duplicate_flow.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/loaderr.py (renamed from test/scripts/loaderr.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/reqerr.py (renamed from test/scripts/reqerr.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/starterr.py (renamed from test/scripts/starterr.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/stream_modify.py (renamed from test/scripts/stream_modify.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/syntaxerr.py (renamed from test/scripts/syntaxerr.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/tcp_stream_modify.py (renamed from test/scripts/tcp_stream_modify.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/scripts/unloaderr.py (renamed from test/scripts/unloaderr.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_app.py (renamed from test/test_app.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_cmdline.py (renamed from test/test_cmdline.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_console.py (renamed from test/test_console.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_console_common.py (renamed from test/test_console_common.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_console_help.py (renamed from test/test_console_help.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_console_palettes.py (renamed from test/test_console_palettes.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_console_pathedit.py (renamed from test/test_console_pathedit.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_contentview.py (renamed from test/test_contentview.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_controller.py (renamed from test/test_controller.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_custom_contentview.py (renamed from test/test_custom_contentview.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_dump.py (renamed from test/test_dump.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_examples.py (renamed from test/test_examples.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_filt.py (renamed from test/test_filt.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_flow.py (renamed from test/test_flow.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_flow_export.py (renamed from test/test_flow_export.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_flow_format_compat.py (renamed from test/test_flow_format_compat.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_fuzzing.py (renamed from test/test_fuzzing.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_platform_pf.py (renamed from test/test_platform_pf.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_protocol_http1.py (renamed from test/test_protocol_http1.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_protocol_http2.py (renamed from test/test_protocol_http2.py) | 2 | ||||
-rw-r--r-- | test/mitmproxy/test_proxy.py (renamed from test/test_proxy.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_script.py (renamed from test/test_script.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_server.py (renamed from test/test_server.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/test_utils.py (renamed from test/test_utils.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/tools/1024example (renamed from test/tools/1024example) | 0 | ||||
-rw-r--r-- | test/mitmproxy/tools/ab.exe (renamed from test/tools/ab.exe) | bin | 82944 -> 82944 bytes | |||
-rw-r--r-- | test/mitmproxy/tools/bench.py (renamed from test/tools/bench.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/tools/benchtool.py (renamed from test/tools/benchtool.py) | 0 | ||||
-rw-r--r--[-rwxr-xr-x] | test/mitmproxy/tools/getcert (renamed from test/tools/getcert) | 0 | ||||
-rw-r--r-- | test/mitmproxy/tools/inspect_dumpfile.py (renamed from test/tools/inspect_dumpfile.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/tools/memoryleak.py (renamed from test/tools/memoryleak.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/tools/passive_close.py (renamed from test/tools/passive_close.py) | 0 | ||||
-rw-r--r--[-rwxr-xr-x] | test/mitmproxy/tools/testpatt (renamed from test/tools/testpatt) | 0 | ||||
-rw-r--r-- | test/mitmproxy/tservers.py (renamed from test/tservers.py) | 0 | ||||
-rw-r--r-- | test/mitmproxy/tutils.py (renamed from test/tutils.py) | 0 | ||||
-rw-r--r-- | test/netlib/__init__.py | 0 | ||||
-rw-r--r-- | test/netlib/data/clientcert/.gitignore | 3 | ||||
-rw-r--r-- | test/netlib/data/clientcert/client.cnf | 5 | ||||
-rw-r--r-- | test/netlib/data/clientcert/client.pem | 42 | ||||
-rw-r--r-- | test/netlib/data/clientcert/make | 8 | ||||
-rw-r--r-- | test/netlib/data/dercert | bin | 0 -> 1838 bytes | |||
-rw-r--r-- | test/netlib/data/dhparam.pem | 13 | ||||
-rw-r--r-- | test/netlib/data/htpasswd | 1 | ||||
-rw-r--r-- | test/netlib/data/server.crt | 14 | ||||
-rw-r--r-- | test/netlib/data/server.key | 15 | ||||
-rw-r--r-- | test/netlib/data/text_cert | 145 | ||||
-rw-r--r-- | test/netlib/data/text_cert_2 | 39 | ||||
-rw-r--r-- | test/netlib/data/text_cert_weird1 | 31 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/9da13359.0 | 21 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/generate.py | 68 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/self-signed.crt | 19 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/self-signed.key | 27 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/trusted-leaf.crt | 18 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/trusted-leaf.key | 27 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/trusted-root.crt | 21 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/trusted-root.key | 27 | ||||
-rw-r--r-- | test/netlib/data/verificationcerts/trusted-root.srl | 1 | ||||
-rw-r--r-- | test/netlib/http/__init__.py | 0 | ||||
-rw-r--r-- | test/netlib/http/http1/__init__.py | 0 | ||||
-rw-r--r-- | test/netlib/http/http1/test_assemble.py | 102 | ||||
-rw-r--r-- | test/netlib/http/http1/test_read.py | 333 | ||||
-rw-r--r-- | test/netlib/http/http2/__init__.py | 0 | ||||
-rw-r--r-- | test/netlib/http/http2/test_connections.py | 540 | ||||
-rw-r--r-- | test/netlib/http/test_authentication.py | 122 | ||||
-rw-r--r-- | test/netlib/http/test_cookies.py | 218 | ||||
-rw-r--r-- | test/netlib/http/test_headers.py | 152 | ||||
-rw-r--r-- | test/netlib/http/test_message.py | 153 | ||||
-rw-r--r-- | test/netlib/http/test_request.py | 238 | ||||
-rw-r--r-- | test/netlib/http/test_response.py | 102 | ||||
-rw-r--r-- | test/netlib/http/test_status_codes.py | 6 | ||||
-rw-r--r-- | test/netlib/http/test_user_agents.py | 6 | ||||
-rw-r--r-- | test/netlib/test_certutils.py | 155 | ||||
-rw-r--r-- | test/netlib/test_encoding.py | 37 | ||||
-rw-r--r-- | test/netlib/test_imports.py | 1 | ||||
-rw-r--r-- | test/netlib/test_odict.py | 153 | ||||
-rw-r--r-- | test/netlib/test_socks.py | 149 | ||||
-rw-r--r-- | test/netlib/test_tcp.py | 795 | ||||
-rw-r--r-- | test/netlib/test_utils.py | 141 | ||||
-rw-r--r-- | test/netlib/test_version_check.py | 38 | ||||
-rw-r--r-- | test/netlib/test_wsgi.py | 106 | ||||
-rw-r--r-- | test/netlib/tools/getcertnames | 27 | ||||
-rw-r--r-- | test/netlib/websockets/__init__.py | 0 | ||||
-rw-r--r-- | test/netlib/websockets/test_websockets.py | 266 | ||||
-rw-r--r-- | test/pathod/data/clientcert/.gitignore | 3 | ||||
-rw-r--r-- | test/pathod/data/clientcert/client.cnf | 5 | ||||
-rw-r--r-- | test/pathod/data/clientcert/client.pem | 42 | ||||
-rw-r--r-- | test/pathod/data/clientcert/make | 8 | ||||
-rw-r--r-- | test/pathod/data/file | 1 | ||||
-rw-r--r-- | test/pathod/data/request | 1 | ||||
-rw-r--r-- | test/pathod/data/response | 1 | ||||
-rw-r--r-- | test/pathod/data/testkey.pem | 68 | ||||
-rw-r--r-- | test/pathod/scripts/generate.sh | 17 | ||||
-rw-r--r-- | test/pathod/scripts/openssl.cnf | 39 | ||||
-rw-r--r-- | test/pathod/test_app.py | 85 | ||||
-rw-r--r-- | test/pathod/test_language_actions.py | 135 | ||||
-rw-r--r-- | test/pathod/test_language_base.py | 352 | ||||
-rw-r--r-- | test/pathod/test_language_generators.py | 42 | ||||
-rw-r--r-- | test/pathod/test_language_http.py | 358 | ||||
-rw-r--r-- | test/pathod/test_language_http2.py | 233 | ||||
-rw-r--r-- | test/pathod/test_language_websocket.py | 142 | ||||
-rw-r--r-- | test/pathod/test_language_writer.py | 91 | ||||
-rw-r--r-- | test/pathod/test_log.py | 25 | ||||
-rw-r--r-- | test/pathod/test_pathoc.py | 310 | ||||
-rw-r--r-- | test/pathod/test_pathoc_cmdline.py | 59 | ||||
-rw-r--r-- | test/pathod/test_pathod.py | 289 | ||||
-rw-r--r-- | test/pathod/test_pathod_cmdline.py | 85 | ||||
-rw-r--r-- | test/pathod/test_test.py | 45 | ||||
-rw-r--r-- | test/pathod/test_utils.py | 39 | ||||
-rw-r--r-- | test/pathod/tutils.py | 128 |
170 files changed, 6998 insertions, 10 deletions
diff --git a/test/__init__.py b/test/__init__.py index 61d03152..e69de29b 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,9 +0,0 @@ -from __future__ import (print_function, absolute_import, division) - -# Silence third-party modules -import logging -logging.getLogger("hyper").setLevel(logging.WARNING) -logging.getLogger("requests").setLevel(logging.WARNING) -logging.getLogger("passlib").setLevel(logging.WARNING) -logging.getLogger("PIL").setLevel(logging.WARNING) -logging.getLogger("tornado").setLevel(logging.WARNING) diff --git a/test/mitmproxy/__init__.py b/test/mitmproxy/__init__.py new file mode 100644 index 00000000..61d03152 --- /dev/null +++ b/test/mitmproxy/__init__.py @@ -0,0 +1,9 @@ +from __future__ import (print_function, absolute_import, division) + +# Silence third-party modules +import logging +logging.getLogger("hyper").setLevel(logging.WARNING) +logging.getLogger("requests").setLevel(logging.WARNING) +logging.getLogger("passlib").setLevel(logging.WARNING) +logging.getLogger("PIL").setLevel(logging.WARNING) +logging.getLogger("tornado").setLevel(logging.WARNING) diff --git a/test/completion/aaa b/test/mitmproxy/completion/aaa index e69de29b..e69de29b 100644 --- a/test/completion/aaa +++ b/test/mitmproxy/completion/aaa diff --git a/test/completion/aab b/test/mitmproxy/completion/aab index e69de29b..e69de29b 100644 --- a/test/completion/aab +++ b/test/mitmproxy/completion/aab diff --git a/test/completion/aac b/test/mitmproxy/completion/aac index e69de29b..e69de29b 100644 --- a/test/completion/aac +++ b/test/mitmproxy/completion/aac diff --git a/test/data/1.css b/test/mitmproxy/data/1.css index 33387ca7..33387ca7 100644 --- a/test/data/1.css +++ b/test/mitmproxy/data/1.css diff --git a/test/data/amf01 b/test/mitmproxy/data/amf01 Binary files differindex c8fc261d..c8fc261d 100644 --- a/test/data/amf01 +++ b/test/mitmproxy/data/amf01 diff --git a/test/data/amf02 b/test/mitmproxy/data/amf02 Binary files differindex ba69f130..ba69f130 100644 --- a/test/data/amf02 +++ b/test/mitmproxy/data/amf02 diff --git a/test/data/amf03 b/test/mitmproxy/data/amf03 Binary files differindex d9fa736a..d9fa736a 100644 --- a/test/data/amf03 +++ b/test/mitmproxy/data/amf03 diff --git a/test/data/clientcert/.gitignore b/test/mitmproxy/data/clientcert/.gitignore index 07bc53d2..07bc53d2 100644 --- a/test/data/clientcert/.gitignore +++ b/test/mitmproxy/data/clientcert/.gitignore diff --git a/test/data/clientcert/127.0.0.1.pem b/test/mitmproxy/data/clientcert/127.0.0.1.pem index d7093b76..d7093b76 100644 --- a/test/data/clientcert/127.0.0.1.pem +++ b/test/mitmproxy/data/clientcert/127.0.0.1.pem diff --git a/test/data/clientcert/client.cnf b/test/mitmproxy/data/clientcert/client.cnf index 5046a944..5046a944 100644 --- a/test/data/clientcert/client.cnf +++ b/test/mitmproxy/data/clientcert/client.cnf diff --git a/test/data/clientcert/client.pem b/test/mitmproxy/data/clientcert/client.pem index 322e07e0..322e07e0 100644 --- a/test/data/clientcert/client.pem +++ b/test/mitmproxy/data/clientcert/client.pem diff --git a/test/data/clientcert/make b/test/mitmproxy/data/clientcert/make index e829952d..e829952d 100755..100644 --- a/test/data/clientcert/make +++ b/test/mitmproxy/data/clientcert/make diff --git a/test/data/confdir/mitmproxy-ca-cert.cer b/test/mitmproxy/data/confdir/mitmproxy-ca-cert.cer index cc7f8f19..cc7f8f19 100644 --- a/test/data/confdir/mitmproxy-ca-cert.cer +++ b/test/mitmproxy/data/confdir/mitmproxy-ca-cert.cer diff --git a/test/data/confdir/mitmproxy-ca-cert.p12 b/test/mitmproxy/data/confdir/mitmproxy-ca-cert.p12 Binary files differindex d4cec0d4..d4cec0d4 100644 --- a/test/data/confdir/mitmproxy-ca-cert.p12 +++ b/test/mitmproxy/data/confdir/mitmproxy-ca-cert.p12 diff --git a/test/data/confdir/mitmproxy-ca-cert.pem b/test/mitmproxy/data/confdir/mitmproxy-ca-cert.pem index cc7f8f19..cc7f8f19 100644 --- a/test/data/confdir/mitmproxy-ca-cert.pem +++ b/test/mitmproxy/data/confdir/mitmproxy-ca-cert.pem diff --git a/test/data/confdir/mitmproxy-ca.pem b/test/mitmproxy/data/confdir/mitmproxy-ca.pem index 2a2343a6..2a2343a6 100644 --- a/test/data/confdir/mitmproxy-ca.pem +++ b/test/mitmproxy/data/confdir/mitmproxy-ca.pem diff --git a/test/data/dercert b/test/mitmproxy/data/dercert Binary files differindex 370252af..370252af 100644 --- a/test/data/dercert +++ b/test/mitmproxy/data/dercert diff --git a/test/data/dumpfile-012 b/test/mitmproxy/data/dumpfile-012 index 49c2350d..49c2350d 100644 --- a/test/data/dumpfile-012 +++ b/test/mitmproxy/data/dumpfile-012 diff --git a/test/data/dumpfile-013 b/test/mitmproxy/data/dumpfile-013 index ede06f23..ede06f23 100644 --- a/test/data/dumpfile-013 +++ b/test/mitmproxy/data/dumpfile-013 diff --git a/test/data/htpasswd b/test/mitmproxy/data/htpasswd index 54c95b8c..54c95b8c 100644 --- a/test/data/htpasswd +++ b/test/mitmproxy/data/htpasswd diff --git a/test/data/htpasswd.invalid b/test/mitmproxy/data/htpasswd.invalid index 257cc564..257cc564 100644 --- a/test/data/htpasswd.invalid +++ b/test/mitmproxy/data/htpasswd.invalid diff --git a/test/data/image-err1.jpg b/test/mitmproxy/data/image-err1.jpg Binary files differindex 1b251e6e..1b251e6e 100644 --- a/test/data/image-err1.jpg +++ b/test/mitmproxy/data/image-err1.jpg diff --git a/test/data/image.gif b/test/mitmproxy/data/image.gif Binary files differindex 91c53284..91c53284 100644 --- a/test/data/image.gif +++ b/test/mitmproxy/data/image.gif diff --git a/test/data/image.ico b/test/mitmproxy/data/image.ico Binary files differindex 4a8421dc..4a8421dc 100644 --- a/test/data/image.ico +++ b/test/mitmproxy/data/image.ico diff --git a/test/data/image.jpg b/test/mitmproxy/data/image.jpg Binary files differindex 6cbe081e..6cbe081e 100644 --- a/test/data/image.jpg +++ b/test/mitmproxy/data/image.jpg diff --git a/test/data/image.png b/test/mitmproxy/data/image.png Binary files differindex 33143e77..33143e77 100644 --- a/test/data/image.png +++ b/test/mitmproxy/data/image.png diff --git a/test/data/no_common_name.pem b/test/mitmproxy/data/no_common_name.pem index fc271a0e..fc271a0e 100644 --- a/test/data/no_common_name.pem +++ b/test/mitmproxy/data/no_common_name.pem diff --git a/test/data/pf01 b/test/mitmproxy/data/pf01 index 3139a289..3139a289 100644 --- a/test/data/pf01 +++ b/test/mitmproxy/data/pf01 diff --git a/test/data/pf02 b/test/mitmproxy/data/pf02 index e4dc18b3..e4dc18b3 100644 --- a/test/data/pf02 +++ b/test/mitmproxy/data/pf02 diff --git a/test/data/protobuf01 b/test/mitmproxy/data/protobuf01 index fbfdbff3..fbfdbff3 100644 --- a/test/data/protobuf01 +++ b/test/mitmproxy/data/protobuf01 diff --git a/test/data/replace b/test/mitmproxy/data/replace index ad8e760a..ad8e760a 100644 --- a/test/data/replace +++ b/test/mitmproxy/data/replace diff --git a/test/data/testkey.pem b/test/mitmproxy/data/testkey.pem index af8d9d8f..af8d9d8f 100644 --- a/test/data/testkey.pem +++ b/test/mitmproxy/data/testkey.pem diff --git a/test/data/trusted-cadir/8117bdb9.0 b/test/mitmproxy/data/trusted-cadir/8117bdb9.0 index ae78b546..ae78b546 100644 --- a/test/data/trusted-cadir/8117bdb9.0 +++ b/test/mitmproxy/data/trusted-cadir/8117bdb9.0 diff --git a/test/data/trusted-cadir/9d45e6a9.0 b/test/mitmproxy/data/trusted-cadir/9d45e6a9.0 index ae78b546..ae78b546 100644 --- a/test/data/trusted-cadir/9d45e6a9.0 +++ b/test/mitmproxy/data/trusted-cadir/9d45e6a9.0 diff --git a/test/data/trusted-cadir/trusted-ca.pem b/test/mitmproxy/data/trusted-cadir/trusted-ca.pem index ae78b546..ae78b546 100644 --- a/test/data/trusted-cadir/trusted-ca.pem +++ b/test/mitmproxy/data/trusted-cadir/trusted-ca.pem diff --git a/test/data/trusted-server.crt b/test/mitmproxy/data/trusted-server.crt index 76f8559a..76f8559a 100644 --- a/test/data/trusted-server.crt +++ b/test/mitmproxy/data/trusted-server.crt diff --git a/test/data/untrusted-server.crt b/test/mitmproxy/data/untrusted-server.crt index 62e58601..62e58601 100644 --- a/test/data/untrusted-server.crt +++ b/test/mitmproxy/data/untrusted-server.crt diff --git a/test/fuzzing/.env b/test/mitmproxy/fuzzing/.env index 82ae6a8d..82ae6a8d 100644 --- a/test/fuzzing/.env +++ b/test/mitmproxy/fuzzing/.env diff --git a/test/fuzzing/README b/test/mitmproxy/fuzzing/README index 2760506f..2760506f 100644 --- a/test/fuzzing/README +++ b/test/mitmproxy/fuzzing/README diff --git a/test/fuzzing/client_patterns b/test/mitmproxy/fuzzing/client_patterns index 83457b6f..83457b6f 100644 --- a/test/fuzzing/client_patterns +++ b/test/mitmproxy/fuzzing/client_patterns diff --git a/test/fuzzing/go_proxy b/test/mitmproxy/fuzzing/go_proxy index ea29400f..ea29400f 100755..100644 --- a/test/fuzzing/go_proxy +++ b/test/mitmproxy/fuzzing/go_proxy diff --git a/test/fuzzing/reverse_patterns b/test/mitmproxy/fuzzing/reverse_patterns index 8d1d76a2..8d1d76a2 100644 --- a/test/fuzzing/reverse_patterns +++ b/test/mitmproxy/fuzzing/reverse_patterns diff --git a/test/fuzzing/straight_stream b/test/mitmproxy/fuzzing/straight_stream index 41e2a6e1..41e2a6e1 100644 --- a/test/fuzzing/straight_stream +++ b/test/mitmproxy/fuzzing/straight_stream diff --git a/test/fuzzing/straight_stream_patterns b/test/mitmproxy/fuzzing/straight_stream_patterns index 93a066e6..93a066e6 100644 --- a/test/fuzzing/straight_stream_patterns +++ b/test/mitmproxy/fuzzing/straight_stream_patterns diff --git a/test/fuzzing/straight_stream_ssl b/test/mitmproxy/fuzzing/straight_stream_ssl index 708ff0b3..708ff0b3 100644 --- a/test/fuzzing/straight_stream_ssl +++ b/test/mitmproxy/fuzzing/straight_stream_ssl diff --git a/test/mock_urwid.py b/test/mitmproxy/mock_urwid.py index 191210bf..191210bf 100644 --- a/test/mock_urwid.py +++ b/test/mitmproxy/mock_urwid.py diff --git a/test/scripts/a.py b/test/mitmproxy/scripts/a.py index d4272ac8..d4272ac8 100644 --- a/test/scripts/a.py +++ b/test/mitmproxy/scripts/a.py diff --git a/test/scripts/a_helper.py b/test/mitmproxy/scripts/a_helper.py index e1f1c649..e1f1c649 100644 --- a/test/scripts/a_helper.py +++ b/test/mitmproxy/scripts/a_helper.py diff --git a/test/scripts/all.py b/test/mitmproxy/scripts/all.py index dad2aade..dad2aade 100644 --- a/test/scripts/all.py +++ b/test/mitmproxy/scripts/all.py diff --git a/test/scripts/concurrent_decorator.py b/test/mitmproxy/scripts/concurrent_decorator.py index f6feda1d..f6feda1d 100644 --- a/test/scripts/concurrent_decorator.py +++ b/test/mitmproxy/scripts/concurrent_decorator.py diff --git a/test/scripts/concurrent_decorator_err.py b/test/mitmproxy/scripts/concurrent_decorator_err.py index 00fd8dad..00fd8dad 100644 --- a/test/scripts/concurrent_decorator_err.py +++ b/test/mitmproxy/scripts/concurrent_decorator_err.py diff --git a/test/scripts/duplicate_flow.py b/test/mitmproxy/scripts/duplicate_flow.py index e13af786..e13af786 100644 --- a/test/scripts/duplicate_flow.py +++ b/test/mitmproxy/scripts/duplicate_flow.py diff --git a/test/scripts/loaderr.py b/test/mitmproxy/scripts/loaderr.py index 8dc4d56d..8dc4d56d 100644 --- a/test/scripts/loaderr.py +++ b/test/mitmproxy/scripts/loaderr.py diff --git a/test/scripts/reqerr.py b/test/mitmproxy/scripts/reqerr.py index e7c503a8..e7c503a8 100644 --- a/test/scripts/reqerr.py +++ b/test/mitmproxy/scripts/reqerr.py diff --git a/test/scripts/starterr.py b/test/mitmproxy/scripts/starterr.py index b217bdfe..b217bdfe 100644 --- a/test/scripts/starterr.py +++ b/test/mitmproxy/scripts/starterr.py diff --git a/test/scripts/stream_modify.py b/test/mitmproxy/scripts/stream_modify.py index e26d83f1..e26d83f1 100644 --- a/test/scripts/stream_modify.py +++ b/test/mitmproxy/scripts/stream_modify.py diff --git a/test/scripts/syntaxerr.py b/test/mitmproxy/scripts/syntaxerr.py index 219d6b84..219d6b84 100644 --- a/test/scripts/syntaxerr.py +++ b/test/mitmproxy/scripts/syntaxerr.py diff --git a/test/scripts/tcp_stream_modify.py b/test/mitmproxy/scripts/tcp_stream_modify.py index 93b0d5c8..93b0d5c8 100644 --- a/test/scripts/tcp_stream_modify.py +++ b/test/mitmproxy/scripts/tcp_stream_modify.py diff --git a/test/scripts/unloaderr.py b/test/mitmproxy/scripts/unloaderr.py index fba02734..fba02734 100644 --- a/test/scripts/unloaderr.py +++ b/test/mitmproxy/scripts/unloaderr.py diff --git a/test/test_app.py b/test/mitmproxy/test_app.py index 577a70a5..577a70a5 100644 --- a/test/test_app.py +++ b/test/mitmproxy/test_app.py diff --git a/test/test_cmdline.py b/test/mitmproxy/test_cmdline.py index 9b3317aa..9b3317aa 100644 --- a/test/test_cmdline.py +++ b/test/mitmproxy/test_cmdline.py diff --git a/test/test_console.py b/test/mitmproxy/test_console.py index e64ed44a..e64ed44a 100644 --- a/test/test_console.py +++ b/test/mitmproxy/test_console.py diff --git a/test/test_console_common.py b/test/mitmproxy/test_console_common.py index deba5f6c..deba5f6c 100644 --- a/test/test_console_common.py +++ b/test/mitmproxy/test_console_common.py diff --git a/test/test_console_help.py b/test/mitmproxy/test_console_help.py index f1a71faf..f1a71faf 100644 --- a/test/test_console_help.py +++ b/test/mitmproxy/test_console_help.py diff --git a/test/test_console_palettes.py b/test/mitmproxy/test_console_palettes.py index ac33f83d..ac33f83d 100644 --- a/test/test_console_palettes.py +++ b/test/mitmproxy/test_console_palettes.py diff --git a/test/test_console_pathedit.py b/test/mitmproxy/test_console_pathedit.py index 940351f5..940351f5 100644 --- a/test/test_console_pathedit.py +++ b/test/mitmproxy/test_console_pathedit.py diff --git a/test/test_contentview.py b/test/mitmproxy/test_contentview.py index af80d63a..af80d63a 100644 --- a/test/test_contentview.py +++ b/test/mitmproxy/test_contentview.py diff --git a/test/test_controller.py b/test/mitmproxy/test_controller.py index ffc7d433..ffc7d433 100644 --- a/test/test_controller.py +++ b/test/mitmproxy/test_controller.py diff --git a/test/test_custom_contentview.py b/test/mitmproxy/test_custom_contentview.py index adc4109b..adc4109b 100644 --- a/test/test_custom_contentview.py +++ b/test/mitmproxy/test_custom_contentview.py diff --git a/test/test_dump.py b/test/mitmproxy/test_dump.py index dbd0c653..dbd0c653 100644 --- a/test/test_dump.py +++ b/test/mitmproxy/test_dump.py diff --git a/test/test_examples.py b/test/mitmproxy/test_examples.py index bb7c596b..bb7c596b 100644 --- a/test/test_examples.py +++ b/test/mitmproxy/test_examples.py diff --git a/test/test_filt.py b/test/mitmproxy/test_filt.py index e6873c7d..e6873c7d 100644 --- a/test/test_filt.py +++ b/test/mitmproxy/test_filt.py diff --git a/test/test_flow.py b/test/mitmproxy/test_flow.py index b122489f..b122489f 100644 --- a/test/test_flow.py +++ b/test/mitmproxy/test_flow.py diff --git a/test/test_flow_export.py b/test/mitmproxy/test_flow_export.py index e5e9c0a3..e5e9c0a3 100644 --- a/test/test_flow_export.py +++ b/test/mitmproxy/test_flow_export.py diff --git a/test/test_flow_format_compat.py b/test/mitmproxy/test_flow_format_compat.py index 232f5473..232f5473 100644 --- a/test/test_flow_format_compat.py +++ b/test/mitmproxy/test_flow_format_compat.py diff --git a/test/test_fuzzing.py b/test/mitmproxy/test_fuzzing.py index cec64f58..cec64f58 100644 --- a/test/test_fuzzing.py +++ b/test/mitmproxy/test_fuzzing.py diff --git a/test/test_platform_pf.py b/test/mitmproxy/test_platform_pf.py index 8994ee0d..8994ee0d 100644 --- a/test/test_platform_pf.py +++ b/test/mitmproxy/test_platform_pf.py diff --git a/test/test_protocol_http1.py b/test/mitmproxy/test_protocol_http1.py index 13e0eabe..13e0eabe 100644 --- a/test/test_protocol_http1.py +++ b/test/mitmproxy/test_protocol_http1.py diff --git a/test/test_protocol_http2.py b/test/mitmproxy/test_protocol_http2.py index c2c736af..6d3bb43f 100644 --- a/test/test_protocol_http2.py +++ b/test/mitmproxy/test_protocol_http2.py @@ -26,7 +26,7 @@ import h2 from . import tservers requires_alpn = pytest.mark.skipif( - not OpenSSL._util.lib.Cryptography_HAS_ALPN, + not netlib.tcp.HAS_ALPN, reason="requires OpenSSL with ALPN support") diff --git a/test/test_proxy.py b/test/mitmproxy/test_proxy.py index 27ae70a8..27ae70a8 100644 --- a/test/test_proxy.py +++ b/test/mitmproxy/test_proxy.py diff --git a/test/test_script.py b/test/mitmproxy/test_script.py index f3a6499e..f3a6499e 100644 --- a/test/test_script.py +++ b/test/mitmproxy/test_script.py diff --git a/test/test_server.py b/test/mitmproxy/test_server.py index 1b7e6966..1b7e6966 100644 --- a/test/test_server.py +++ b/test/mitmproxy/test_server.py diff --git a/test/test_utils.py b/test/mitmproxy/test_utils.py index 17bf3dbf..17bf3dbf 100644 --- a/test/test_utils.py +++ b/test/mitmproxy/test_utils.py diff --git a/test/tools/1024example b/test/mitmproxy/tools/1024example index 78af7ed0..78af7ed0 100644 --- a/test/tools/1024example +++ b/test/mitmproxy/tools/1024example diff --git a/test/tools/ab.exe b/test/mitmproxy/tools/ab.exe Binary files differindex d68ed0f3..d68ed0f3 100644 --- a/test/tools/ab.exe +++ b/test/mitmproxy/tools/ab.exe diff --git a/test/tools/bench.py b/test/mitmproxy/tools/bench.py index 8127d083..8127d083 100644 --- a/test/tools/bench.py +++ b/test/mitmproxy/tools/bench.py diff --git a/test/tools/benchtool.py b/test/mitmproxy/tools/benchtool.py index a1d80697..a1d80697 100644 --- a/test/tools/benchtool.py +++ b/test/mitmproxy/tools/benchtool.py diff --git a/test/tools/getcert b/test/mitmproxy/tools/getcert index 3bd2bec8..3bd2bec8 100755..100644 --- a/test/tools/getcert +++ b/test/mitmproxy/tools/getcert diff --git a/test/tools/inspect_dumpfile.py b/test/mitmproxy/tools/inspect_dumpfile.py index d15e9e8a..d15e9e8a 100644 --- a/test/tools/inspect_dumpfile.py +++ b/test/mitmproxy/tools/inspect_dumpfile.py diff --git a/test/tools/memoryleak.py b/test/mitmproxy/tools/memoryleak.py index 259309a6..259309a6 100644 --- a/test/tools/memoryleak.py +++ b/test/mitmproxy/tools/memoryleak.py diff --git a/test/tools/passive_close.py b/test/mitmproxy/tools/passive_close.py index 5b1bd451..5b1bd451 100644 --- a/test/tools/passive_close.py +++ b/test/mitmproxy/tools/passive_close.py diff --git a/test/tools/testpatt b/test/mitmproxy/tools/testpatt index b41011c0..b41011c0 100755..100644 --- a/test/tools/testpatt +++ b/test/mitmproxy/tools/testpatt diff --git a/test/tservers.py b/test/mitmproxy/tservers.py index dbc9f7d0..dbc9f7d0 100644 --- a/test/tservers.py +++ b/test/mitmproxy/tservers.py diff --git a/test/tutils.py b/test/mitmproxy/tutils.py index 2ce0884d..2ce0884d 100644 --- a/test/tutils.py +++ b/test/mitmproxy/tutils.py diff --git a/test/netlib/__init__.py b/test/netlib/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/netlib/__init__.py diff --git a/test/netlib/data/clientcert/.gitignore b/test/netlib/data/clientcert/.gitignore new file mode 100644 index 00000000..07bc53d2 --- /dev/null +++ b/test/netlib/data/clientcert/.gitignore @@ -0,0 +1,3 @@ +client.crt +client.key +client.req diff --git a/test/netlib/data/clientcert/client.cnf b/test/netlib/data/clientcert/client.cnf new file mode 100644 index 00000000..5046a944 --- /dev/null +++ b/test/netlib/data/clientcert/client.cnf @@ -0,0 +1,5 @@ +[ ssl_client ] +basicConstraints = CA:FALSE +nsCertType = client +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth diff --git a/test/netlib/data/clientcert/client.pem b/test/netlib/data/clientcert/client.pem new file mode 100644 index 00000000..4927bca2 --- /dev/null +++ b/test/netlib/data/clientcert/client.pem @@ -0,0 +1,42 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0 +EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+ +ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G +3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/ +SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP +G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABAoIBAFE3FV/IDltbmHEP +iky93hbJm+6QgKepFReKpRVTyqb7LaygUvueQyPWQMIriKTsy675nxo8DQr7tQsO +y3YlSZgra/xNMikIB6e82c7K8DgyrDQw/rCqjZB3Xt4VCqsWJDLXnQMSn98lx0g7 +d7Lbf8soUpKWXqfdVpSDTi4fibSX6kshXyfSTpcz4AdoncEpViUfU1xkEEmZrjT8 +1GcCsDC41xdNmzCpqRuZX7DKSFRoB+0hUzsC1oiqM7FD5kixonRd4F5PbRXImIzt +6YCsT2okxTA04jX7yByis7LlOLTlkmLtKQYuc3erOFvwx89s4vW+AeFei+GGNitn +tHfSwbECgYEA7SzV+nN62hAERHlg8cEQT4TxnsWvbronYWcc/ev44eHSPDWL5tPi +GHfSbW6YAq5Wa0I9jMWfXyhOYEC3MZTC5EEeLOB71qVrTwcy/sY66rOrcgjFI76Q +5JFHQ4wy3SWU50KxE0oWJO9LIowprG+pW1vzqC3VF0T7q0FqESrY4LUCgYEA3F7Z +80ndnCUlooJAb+Hfotv7peFf1o6+m1PTRcz1lLnVt5R5lXj86kn+tXEpYZo1RiGR +2rE2N0seeznWCooakHcsBN7/qmFIhhooJNF7yW+JP2I4P2UV5+tJ+8bcs/voUkQD +1x+rGOuMn8nvHBd2+Vharft8eGL2mgooPVI2XusCgYEAlMZpO3+w8pTVeHaDP2MR +7i/AuQ3cbCLNjSX3Y7jgGCFllWspZRRIYXzYPNkA9b2SbBnTLjjRLgnEkFBIGgvs +7O2EFjaCuDRvydUEQhjq4ErwIsopj7B8h0QyZcbOKTbn3uFQ3n68wVJx2Sv/ADHT +FIHrp/WIE96r19Niy34LKXkCgYB2W59VsuOKnMz01l5DeR5C+0HSWxS9SReIl2IO +yEFSKullWyJeLIgyUaGy0990430feKI8whcrZXYumuah7IDN/KOwzhCk8vEfzWao +N7bzfqtJVrh9HA7C7DVlO+6H4JFrtcoWPZUIomJ549w/yz6EN3ckoMC+a/Ck1TW9 +ka1QFwKBgQCywG6TrZz0UmOjyLQZ+8Q4uvZklSW5NAKBkNnyuQ2kd5rzyYgMPE8C +Er8T88fdVIKvkhDyHhwcI7n58xE5Gr7wkwsrk/Hbd9/ZB2GgAPY3cATskK1v1McU +YeX38CU0fUS4aoy26hWQXkViB47IGQ3jWo3ZCtzIJl8DI9/RsBWTnw== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICYDCCAckCAQEwDQYJKoZIhvcNAQEFBQAwKDESMBAGA1UEAxMJbWl0bXByb3h5 +MRIwEAYDVQQKEwltaXRtcHJveHkwHhcNMTMwMTIwMDEwODEzWhcNMTUxMDE3MDEw +ODEzWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UE +ChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0 +EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+ +ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G +3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/ +SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP +G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABMA0GCSqGSIb3DQEBBQUA +A4GBAFvI+cd47B85PQ970n2dU/PlA2/Hb1ldrrXh2guR4hX6vYx/uuk5yRI/n0Rd +KOXJ3czO0bd2Fpe3ZoNpkW0pOSDej/Q+58ScuJd0gWCT/Sh1eRk6ZdC0kusOuWoY +bPOPMkG45LPgUMFOnZEsfJP6P5mZIxlbCvSMFC25nPHWlct7 +-----END CERTIFICATE----- diff --git a/test/netlib/data/clientcert/make b/test/netlib/data/clientcert/make new file mode 100644 index 00000000..d1caea81 --- /dev/null +++ b/test/netlib/data/clientcert/make @@ -0,0 +1,8 @@ +#!/bin/sh + +openssl genrsa -out client.key 2048 +openssl req -key client.key -new -out client.req +openssl x509 -req -days 365 -in client.req -signkey client.key -out client.crt -extfile client.cnf -extensions ssl_client +openssl x509 -req -days 1000 -in client.req -CA ~/.mitmproxy/mitmproxy-ca.pem -CAkey ~/.mitmproxy/mitmproxy-ca.pem -set_serial 00001 -out client.crt -extensions ssl_client +cat client.key client.crt > client.pem +openssl x509 -text -noout -in client.pem diff --git a/test/netlib/data/dercert b/test/netlib/data/dercert Binary files differnew file mode 100644 index 00000000..370252af --- /dev/null +++ b/test/netlib/data/dercert diff --git a/test/netlib/data/dhparam.pem b/test/netlib/data/dhparam.pem new file mode 100644 index 00000000..afb41672 --- /dev/null +++ b/test/netlib/data/dhparam.pem @@ -0,0 +1,13 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEAyT6LzpwVFS3gryIo29J5icvgxCnCebcdSe/NHMkD8dKJf8suFCg3 +O2+dguLakSVif/t6dhImxInJk230HmfC8q93hdcg/j8rLGJYDKu3ik6H//BAHKIv +j5O9yjU3rXCfmVJQic2Nne39sg3CreAepEts2TvYHhVv3TEAzEqCtOuTjgDv0ntJ +Gwpj+BJBRQGG9NvprX1YGJ7WOFBP/hWU7d6tgvE6Xa7T/u9QIKpYHMIkcN/l3ZFB +chZEqVlyrcngtSXCROTPcDOQ6Q8QzhaBJS+Z6rcsd7X+haiQqvoFcmaJ08Ks6LQC +ZIL2EtYJw8V8z7C0igVEBIADZBI6OTbuuhDwRw//zU1uq52Oc48CIZlGxTYG/Evq +o9EWAXUYVzWkDSTeBH1r4z/qLPE2cnhtMxbFxuvK53jGB0emy2y1Ei6IhKshJ5qX +IB/aE7SSHyQ3MDHHkCmQJCsOd4Mo26YX61NZ+n501XjqpCBQ2+DfZCBh8Va2wDyv +A2Ryg9SUz8j0AXViRNMJgJrr446yro/FuJZwnQcO3WQnXeqSBnURqKjmqkeFP+d8 +6mk2tqJaY507lRNqtGlLnj7f5RNoBFJDCLBNurVgfvq9TCVWKDIFD4vZRjCrnl6I +rD693XKIHUCWOjMh1if6omGXKHH40QuME2gNa50+YPn1iYDl88uDbbMCAQI= +-----END DH PARAMETERS----- diff --git a/test/netlib/data/htpasswd b/test/netlib/data/htpasswd new file mode 100644 index 00000000..54c95b8c --- /dev/null +++ b/test/netlib/data/htpasswd @@ -0,0 +1 @@ +test:$apr1$/LkYxy3x$WI4.YbiJlu537jLGEW2eu1 diff --git a/test/netlib/data/server.crt b/test/netlib/data/server.crt new file mode 100644 index 00000000..68f61bac --- /dev/null +++ b/test/netlib/data/server.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICOzCCAaQCCQDC7f5GsEpo9jANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJO +WjEOMAwGA1UECBMFT3RhZ28xEDAOBgNVBAcTB0R1bmVkaW4xDzANBgNVBAoTBm5l +dGxpYjEPMA0GA1UECxMGbmV0bGliMQ8wDQYDVQQDEwZuZXRsaWIwHhcNMTIwNjI0 +MjI0MTU0WhcNMjIwNjIyMjI0MTU0WjBiMQswCQYDVQQGEwJOWjEOMAwGA1UECBMF +T3RhZ28xEDAOBgNVBAcTB0R1bmVkaW4xDzANBgNVBAoTBm5ldGxpYjEPMA0GA1UE +CxMGbmV0bGliMQ8wDQYDVQQDEwZuZXRsaWIwgZ8wDQYJKoZIhvcNAQEBBQADgY0A +MIGJAoGBALJSVEl9y3QUSYuXTH0UjBOPQgS0nHmNWej9hjqnA0KWvEnGY+c6yQeP +/rmwswlKw1iVV5o8kRK9Wej88YWQl/hl/xruyeJgGic0+yqY/FcueZxRudwBcWu2 +7+46aEftwLLRF0GwHZxX/HwWME+TcCXGpXGSG2qs921M4iVeBn5hAgMBAAEwDQYJ +KoZIhvcNAQEFBQADgYEAODZCihEv2yr8zmmQZDrfqg2ChxAoOXWF5+W2F/0LAUBf +2bHP+K4XE6BJWmadX1xKngj7SWrhmmTDp1gBAvXURoDaScOkB1iOCOHoIyalscTR +0FvSHKqFF8fgSlfqS6eYaSbXU3zQolvwP+URzIVnGDqgQCWPtjMqLD3Kd5tuwos= +-----END CERTIFICATE----- diff --git a/test/netlib/data/server.key b/test/netlib/data/server.key new file mode 100644 index 00000000..b1b658ab --- /dev/null +++ b/test/netlib/data/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCyUlRJfct0FEmLl0x9FIwTj0IEtJx5jVno/YY6pwNClrxJxmPn +OskHj/65sLMJSsNYlVeaPJESvVno/PGFkJf4Zf8a7sniYBonNPsqmPxXLnmcUbnc +AXFrtu/uOmhH7cCy0RdBsB2cV/x8FjBPk3AlxqVxkhtqrPdtTOIlXgZ+YQIDAQAB +AoGAQEpGcSiVTYhy64zk2sOprPOdTa0ALSK1I7cjycmk90D5KXAJXLho+f0ETVZT +dioqO6m8J7NmamcyHznyqcDzyNRqD2hEBDGVRJWmpOjIER/JwWLNNbpeVjsMHV8I +40P5rZMOhBPYlwECSC5NtMwaN472fyGNNze8u37IZKiER/ECQQDe1iY5AG3CgkP3 +tEZB3Vtzcn4PoOr3Utyn1YER34lPqAmeAsWUhmAVEfR3N1HDe1VFD9s2BidhBn1a +/Bgqxz4DAkEAzNw0m+uO0WkD7aEYRBW7SbXCX+3xsbVToIWC1jXFG+XDzSWn++c1 +DMXEElzEJxPDA+FzQUvRTml4P92bTAbGywJAS9H7wWtm7Ubbj33UZfbGdhqfz/uF +109naufXedhgZS0c0JnK1oV+Tc0FLEczV9swIUaK5O/lGDtYDcw3AN84NwJBAIw5 +/1jrOOtm8uVp6+5O4dBmthJsEZEPCZtLSG/Qhoe+EvUN3Zq0fL+tb7USAsKs6ERz +wizj9PWzhDhTPMYhrVkCQGIponZHx6VqiFyLgYUH9+gDTjBhYyI+6yMTYzcRweyL +9Suc2NkS3X2Lp+wCjvVZdwGtStp6Vo8z02b3giIsAIY= +-----END RSA PRIVATE KEY----- diff --git a/test/netlib/data/text_cert b/test/netlib/data/text_cert new file mode 100644 index 00000000..36ca33b9 --- /dev/null +++ b/test/netlib/data/text_cert @@ -0,0 +1,145 @@ +-----BEGIN CERTIFICATE----- +MIIadTCCGd6gAwIBAgIGR09PUAFtMA0GCSqGSIb3DQEBBQUAMEYxCzAJBgNVBAYT +AlVTMRMwEQYDVQQKEwpHb29nbGUgSW5jMSIwIAYDVQQDExlHb29nbGUgSW50ZXJu +ZXQgQXV0aG9yaXR5MB4XDTEyMDExNzEyNTUwNFoXDTEzMDExNzEyNTUwNFowTDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEzARBgNVBAoTCkdvb2ds +ZSBJbmMxEzARBgNVBAMTCmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A +MIGJAoGBALofcxR2fud5cyFIeld9pj2vGB5GH0y9tmAYa5t33xbJguKKX/el3tXA +KMNiT1SZzu8ELJ1Ey0GcBAgHA9jVPQd0LGdbEtNIxjblAsWAD/FZlSt8X87h7C5w +2JSefOani0qgQqU6sTdsaCUGZ+Eu7D0lBfT5/Vnl2vV+zI3YmDlpAgMBAAGjghhm +MIIYYjAdBgNVHQ4EFgQUL3+JeC/oL9jZhTp3F550LautzV8wHwYDVR0jBBgwFoAU +v8Aw6/VDET5nup6R+/xq2uNrEiQwWwYDVR0fBFQwUjBQoE6gTIZKaHR0cDovL3d3 +dy5nc3RhdGljLmNvbS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS9Hb29nbGVJbnRl +cm5ldEF1dGhvcml0eS5jcmwwZgYIKwYBBQUHAQEEWjBYMFYGCCsGAQUFBzAChkpo +dHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9yaXR5L0dv +b2dsZUludGVybmV0QXV0aG9yaXR5LmNydDCCF1kGA1UdEQSCF1AwghdMggpnb29n +bGUuY29tggwqLmdvb2dsZS5jb22CCyouZ29vZ2xlLmFjggsqLmdvb2dsZS5hZIIL +Ki5nb29nbGUuYWWCCyouZ29vZ2xlLmFmggsqLmdvb2dsZS5hZ4ILKi5nb29nbGUu +YW2CCyouZ29vZ2xlLmFzggsqLmdvb2dsZS5hdIILKi5nb29nbGUuYXqCCyouZ29v +Z2xlLmJhggsqLmdvb2dsZS5iZYILKi5nb29nbGUuYmaCCyouZ29vZ2xlLmJnggsq +Lmdvb2dsZS5iaYILKi5nb29nbGUuYmqCCyouZ29vZ2xlLmJzggsqLmdvb2dsZS5i +eYILKi5nb29nbGUuY2GCDCouZ29vZ2xlLmNhdIILKi5nb29nbGUuY2OCCyouZ29v +Z2xlLmNkggsqLmdvb2dsZS5jZoILKi5nb29nbGUuY2eCCyouZ29vZ2xlLmNoggsq +Lmdvb2dsZS5jaYILKi5nb29nbGUuY2yCCyouZ29vZ2xlLmNtggsqLmdvb2dsZS5j +boIOKi5nb29nbGUuY28uYW+CDiouZ29vZ2xlLmNvLmJ3gg4qLmdvb2dsZS5jby5j +a4IOKi5nb29nbGUuY28uY3KCDiouZ29vZ2xlLmNvLmh1gg4qLmdvb2dsZS5jby5p +ZIIOKi5nb29nbGUuY28uaWyCDiouZ29vZ2xlLmNvLmltgg4qLmdvb2dsZS5jby5p +boIOKi5nb29nbGUuY28uamWCDiouZ29vZ2xlLmNvLmpwgg4qLmdvb2dsZS5jby5r +ZYIOKi5nb29nbGUuY28ua3KCDiouZ29vZ2xlLmNvLmxzgg4qLmdvb2dsZS5jby5t +YYIOKi5nb29nbGUuY28ubXqCDiouZ29vZ2xlLmNvLm56gg4qLmdvb2dsZS5jby50 +aIIOKi5nb29nbGUuY28udHqCDiouZ29vZ2xlLmNvLnVngg4qLmdvb2dsZS5jby51 +a4IOKi5nb29nbGUuY28udXqCDiouZ29vZ2xlLmNvLnZlgg4qLmdvb2dsZS5jby52 +aYIOKi5nb29nbGUuY28uemGCDiouZ29vZ2xlLmNvLnptgg4qLmdvb2dsZS5jby56 +d4IPKi5nb29nbGUuY29tLmFmgg8qLmdvb2dsZS5jb20uYWeCDyouZ29vZ2xlLmNv +bS5haYIPKi5nb29nbGUuY29tLmFygg8qLmdvb2dsZS5jb20uYXWCDyouZ29vZ2xl +LmNvbS5iZIIPKi5nb29nbGUuY29tLmJogg8qLmdvb2dsZS5jb20uYm6CDyouZ29v +Z2xlLmNvbS5ib4IPKi5nb29nbGUuY29tLmJygg8qLmdvb2dsZS5jb20uYnmCDyou +Z29vZ2xlLmNvbS5ieoIPKi5nb29nbGUuY29tLmNugg8qLmdvb2dsZS5jb20uY2+C +DyouZ29vZ2xlLmNvbS5jdYIPKi5nb29nbGUuY29tLmN5gg8qLmdvb2dsZS5jb20u +ZG+CDyouZ29vZ2xlLmNvbS5lY4IPKi5nb29nbGUuY29tLmVngg8qLmdvb2dsZS5j +b20uZXSCDyouZ29vZ2xlLmNvbS5maoIPKi5nb29nbGUuY29tLmdlgg8qLmdvb2ds +ZS5jb20uZ2iCDyouZ29vZ2xlLmNvbS5naYIPKi5nb29nbGUuY29tLmdygg8qLmdv +b2dsZS5jb20uZ3SCDyouZ29vZ2xlLmNvbS5oa4IPKi5nb29nbGUuY29tLmlxgg8q +Lmdvb2dsZS5jb20uam2CDyouZ29vZ2xlLmNvbS5qb4IPKi5nb29nbGUuY29tLmto +gg8qLmdvb2dsZS5jb20ua3eCDyouZ29vZ2xlLmNvbS5sYoIPKi5nb29nbGUuY29t +Lmx5gg8qLmdvb2dsZS5jb20ubXSCDyouZ29vZ2xlLmNvbS5teIIPKi5nb29nbGUu +Y29tLm15gg8qLmdvb2dsZS5jb20ubmGCDyouZ29vZ2xlLmNvbS5uZoIPKi5nb29n +bGUuY29tLm5ngg8qLmdvb2dsZS5jb20ubmmCDyouZ29vZ2xlLmNvbS5ucIIPKi5n +b29nbGUuY29tLm5ygg8qLmdvb2dsZS5jb20ub22CDyouZ29vZ2xlLmNvbS5wYYIP +Ki5nb29nbGUuY29tLnBlgg8qLmdvb2dsZS5jb20ucGiCDyouZ29vZ2xlLmNvbS5w +a4IPKi5nb29nbGUuY29tLnBsgg8qLmdvb2dsZS5jb20ucHKCDyouZ29vZ2xlLmNv +bS5weYIPKi5nb29nbGUuY29tLnFhgg8qLmdvb2dsZS5jb20ucnWCDyouZ29vZ2xl +LmNvbS5zYYIPKi5nb29nbGUuY29tLnNigg8qLmdvb2dsZS5jb20uc2eCDyouZ29v +Z2xlLmNvbS5zbIIPKi5nb29nbGUuY29tLnN2gg8qLmdvb2dsZS5jb20udGqCDyou +Z29vZ2xlLmNvbS50boIPKi5nb29nbGUuY29tLnRygg8qLmdvb2dsZS5jb20udHeC +DyouZ29vZ2xlLmNvbS51YYIPKi5nb29nbGUuY29tLnV5gg8qLmdvb2dsZS5jb20u +dmOCDyouZ29vZ2xlLmNvbS52ZYIPKi5nb29nbGUuY29tLnZuggsqLmdvb2dsZS5j +doILKi5nb29nbGUuY3qCCyouZ29vZ2xlLmRlggsqLmdvb2dsZS5kaoILKi5nb29n +bGUuZGuCCyouZ29vZ2xlLmRtggsqLmdvb2dsZS5keoILKi5nb29nbGUuZWWCCyou +Z29vZ2xlLmVzggsqLmdvb2dsZS5maYILKi5nb29nbGUuZm2CCyouZ29vZ2xlLmZy +ggsqLmdvb2dsZS5nYYILKi5nb29nbGUuZ2WCCyouZ29vZ2xlLmdnggsqLmdvb2ds +ZS5nbIILKi5nb29nbGUuZ22CCyouZ29vZ2xlLmdwggsqLmdvb2dsZS5ncoILKi5n +b29nbGUuZ3mCCyouZ29vZ2xlLmhrggsqLmdvb2dsZS5oboILKi5nb29nbGUuaHKC +CyouZ29vZ2xlLmh0ggsqLmdvb2dsZS5odYILKi5nb29nbGUuaWWCCyouZ29vZ2xl +Lmltgg0qLmdvb2dsZS5pbmZvggsqLmdvb2dsZS5pcYILKi5nb29nbGUuaXOCCyou +Z29vZ2xlLml0gg4qLmdvb2dsZS5pdC5hb4ILKi5nb29nbGUuamWCCyouZ29vZ2xl +Lmpvgg0qLmdvb2dsZS5qb2JzggsqLmdvb2dsZS5qcIILKi5nb29nbGUua2eCCyou +Z29vZ2xlLmtpggsqLmdvb2dsZS5reoILKi5nb29nbGUubGGCCyouZ29vZ2xlLmxp +ggsqLmdvb2dsZS5sa4ILKi5nb29nbGUubHSCCyouZ29vZ2xlLmx1ggsqLmdvb2ds +ZS5sdoILKi5nb29nbGUubWSCCyouZ29vZ2xlLm1lggsqLmdvb2dsZS5tZ4ILKi5n +b29nbGUubWuCCyouZ29vZ2xlLm1sggsqLmdvb2dsZS5tboILKi5nb29nbGUubXOC +CyouZ29vZ2xlLm11ggsqLmdvb2dsZS5tdoILKi5nb29nbGUubXeCCyouZ29vZ2xl +Lm5lgg4qLmdvb2dsZS5uZS5qcIIMKi5nb29nbGUubmV0ggsqLmdvb2dsZS5ubIIL +Ki5nb29nbGUubm+CCyouZ29vZ2xlLm5yggsqLmdvb2dsZS5udYIPKi5nb29nbGUu +b2ZmLmFpggsqLmdvb2dsZS5wa4ILKi5nb29nbGUucGyCCyouZ29vZ2xlLnBuggsq +Lmdvb2dsZS5wc4ILKi5nb29nbGUucHSCCyouZ29vZ2xlLnJvggsqLmdvb2dsZS5y +c4ILKi5nb29nbGUucnWCCyouZ29vZ2xlLnJ3ggsqLmdvb2dsZS5zY4ILKi5nb29n +bGUuc2WCCyouZ29vZ2xlLnNoggsqLmdvb2dsZS5zaYILKi5nb29nbGUuc2uCCyou +Z29vZ2xlLnNtggsqLmdvb2dsZS5zboILKi5nb29nbGUuc2+CCyouZ29vZ2xlLnN0 +ggsqLmdvb2dsZS50ZIILKi5nb29nbGUudGeCCyouZ29vZ2xlLnRrggsqLmdvb2ds +ZS50bIILKi5nb29nbGUudG2CCyouZ29vZ2xlLnRuggsqLmdvb2dsZS50b4ILKi5n +b29nbGUudHCCCyouZ29vZ2xlLnR0ggsqLmdvb2dsZS51c4ILKi5nb29nbGUudXqC +CyouZ29vZ2xlLnZnggsqLmdvb2dsZS52dYILKi5nb29nbGUud3OCCWdvb2dsZS5h +Y4IJZ29vZ2xlLmFkgglnb29nbGUuYWWCCWdvb2dsZS5hZoIJZ29vZ2xlLmFnggln +b29nbGUuYW2CCWdvb2dsZS5hc4IJZ29vZ2xlLmF0gglnb29nbGUuYXqCCWdvb2ds +ZS5iYYIJZ29vZ2xlLmJlgglnb29nbGUuYmaCCWdvb2dsZS5iZ4IJZ29vZ2xlLmJp +gglnb29nbGUuYmqCCWdvb2dsZS5ic4IJZ29vZ2xlLmJ5gglnb29nbGUuY2GCCmdv +b2dsZS5jYXSCCWdvb2dsZS5jY4IJZ29vZ2xlLmNkgglnb29nbGUuY2aCCWdvb2ds +ZS5jZ4IJZ29vZ2xlLmNogglnb29nbGUuY2mCCWdvb2dsZS5jbIIJZ29vZ2xlLmNt +gglnb29nbGUuY26CDGdvb2dsZS5jby5hb4IMZ29vZ2xlLmNvLmJ3ggxnb29nbGUu +Y28uY2uCDGdvb2dsZS5jby5jcoIMZ29vZ2xlLmNvLmh1ggxnb29nbGUuY28uaWSC +DGdvb2dsZS5jby5pbIIMZ29vZ2xlLmNvLmltggxnb29nbGUuY28uaW6CDGdvb2ds +ZS5jby5qZYIMZ29vZ2xlLmNvLmpwggxnb29nbGUuY28ua2WCDGdvb2dsZS5jby5r +coIMZ29vZ2xlLmNvLmxzggxnb29nbGUuY28ubWGCDGdvb2dsZS5jby5teoIMZ29v +Z2xlLmNvLm56ggxnb29nbGUuY28udGiCDGdvb2dsZS5jby50eoIMZ29vZ2xlLmNv +LnVnggxnb29nbGUuY28udWuCDGdvb2dsZS5jby51eoIMZ29vZ2xlLmNvLnZlggxn +b29nbGUuY28udmmCDGdvb2dsZS5jby56YYIMZ29vZ2xlLmNvLnptggxnb29nbGUu +Y28ueneCDWdvb2dsZS5jb20uYWaCDWdvb2dsZS5jb20uYWeCDWdvb2dsZS5jb20u +YWmCDWdvb2dsZS5jb20uYXKCDWdvb2dsZS5jb20uYXWCDWdvb2dsZS5jb20uYmSC +DWdvb2dsZS5jb20uYmiCDWdvb2dsZS5jb20uYm6CDWdvb2dsZS5jb20uYm+CDWdv +b2dsZS5jb20uYnKCDWdvb2dsZS5jb20uYnmCDWdvb2dsZS5jb20uYnqCDWdvb2ds +ZS5jb20uY26CDWdvb2dsZS5jb20uY2+CDWdvb2dsZS5jb20uY3WCDWdvb2dsZS5j +b20uY3mCDWdvb2dsZS5jb20uZG+CDWdvb2dsZS5jb20uZWOCDWdvb2dsZS5jb20u +ZWeCDWdvb2dsZS5jb20uZXSCDWdvb2dsZS5jb20uZmqCDWdvb2dsZS5jb20uZ2WC +DWdvb2dsZS5jb20uZ2iCDWdvb2dsZS5jb20uZ2mCDWdvb2dsZS5jb20uZ3KCDWdv +b2dsZS5jb20uZ3SCDWdvb2dsZS5jb20uaGuCDWdvb2dsZS5jb20uaXGCDWdvb2ds +ZS5jb20uam2CDWdvb2dsZS5jb20uam+CDWdvb2dsZS5jb20ua2iCDWdvb2dsZS5j +b20ua3eCDWdvb2dsZS5jb20ubGKCDWdvb2dsZS5jb20ubHmCDWdvb2dsZS5jb20u +bXSCDWdvb2dsZS5jb20ubXiCDWdvb2dsZS5jb20ubXmCDWdvb2dsZS5jb20ubmGC +DWdvb2dsZS5jb20ubmaCDWdvb2dsZS5jb20ubmeCDWdvb2dsZS5jb20ubmmCDWdv +b2dsZS5jb20ubnCCDWdvb2dsZS5jb20ubnKCDWdvb2dsZS5jb20ub22CDWdvb2ds +ZS5jb20ucGGCDWdvb2dsZS5jb20ucGWCDWdvb2dsZS5jb20ucGiCDWdvb2dsZS5j +b20ucGuCDWdvb2dsZS5jb20ucGyCDWdvb2dsZS5jb20ucHKCDWdvb2dsZS5jb20u +cHmCDWdvb2dsZS5jb20ucWGCDWdvb2dsZS5jb20ucnWCDWdvb2dsZS5jb20uc2GC +DWdvb2dsZS5jb20uc2KCDWdvb2dsZS5jb20uc2eCDWdvb2dsZS5jb20uc2yCDWdv +b2dsZS5jb20uc3aCDWdvb2dsZS5jb20udGqCDWdvb2dsZS5jb20udG6CDWdvb2ds +ZS5jb20udHKCDWdvb2dsZS5jb20udHeCDWdvb2dsZS5jb20udWGCDWdvb2dsZS5j +b20udXmCDWdvb2dsZS5jb20udmOCDWdvb2dsZS5jb20udmWCDWdvb2dsZS5jb20u +dm6CCWdvb2dsZS5jdoIJZ29vZ2xlLmN6gglnb29nbGUuZGWCCWdvb2dsZS5kaoIJ +Z29vZ2xlLmRrgglnb29nbGUuZG2CCWdvb2dsZS5keoIJZ29vZ2xlLmVlgglnb29n +bGUuZXOCCWdvb2dsZS5maYIJZ29vZ2xlLmZtgglnb29nbGUuZnKCCWdvb2dsZS5n +YYIJZ29vZ2xlLmdlgglnb29nbGUuZ2eCCWdvb2dsZS5nbIIJZ29vZ2xlLmdtggln +b29nbGUuZ3CCCWdvb2dsZS5ncoIJZ29vZ2xlLmd5gglnb29nbGUuaGuCCWdvb2ds +ZS5oboIJZ29vZ2xlLmhygglnb29nbGUuaHSCCWdvb2dsZS5odYIJZ29vZ2xlLmll +gglnb29nbGUuaW2CC2dvb2dsZS5pbmZvgglnb29nbGUuaXGCCWdvb2dsZS5pc4IJ +Z29vZ2xlLml0ggxnb29nbGUuaXQuYW+CCWdvb2dsZS5qZYIJZ29vZ2xlLmpvggtn +b29nbGUuam9ic4IJZ29vZ2xlLmpwgglnb29nbGUua2eCCWdvb2dsZS5raYIJZ29v +Z2xlLmt6gglnb29nbGUubGGCCWdvb2dsZS5saYIJZ29vZ2xlLmxrgglnb29nbGUu +bHSCCWdvb2dsZS5sdYIJZ29vZ2xlLmx2gglnb29nbGUubWSCCWdvb2dsZS5tZYIJ +Z29vZ2xlLm1ngglnb29nbGUubWuCCWdvb2dsZS5tbIIJZ29vZ2xlLm1ugglnb29n +bGUubXOCCWdvb2dsZS5tdYIJZ29vZ2xlLm12gglnb29nbGUubXeCCWdvb2dsZS5u +ZYIMZ29vZ2xlLm5lLmpwggpnb29nbGUubmV0gglnb29nbGUubmyCCWdvb2dsZS5u +b4IJZ29vZ2xlLm5ygglnb29nbGUubnWCDWdvb2dsZS5vZmYuYWmCCWdvb2dsZS5w +a4IJZ29vZ2xlLnBsgglnb29nbGUucG6CCWdvb2dsZS5wc4IJZ29vZ2xlLnB0ggln +b29nbGUucm+CCWdvb2dsZS5yc4IJZ29vZ2xlLnJ1gglnb29nbGUucneCCWdvb2ds +ZS5zY4IJZ29vZ2xlLnNlgglnb29nbGUuc2iCCWdvb2dsZS5zaYIJZ29vZ2xlLnNr +gglnb29nbGUuc22CCWdvb2dsZS5zboIJZ29vZ2xlLnNvgglnb29nbGUuc3SCCWdv +b2dsZS50ZIIJZ29vZ2xlLnRngglnb29nbGUudGuCCWdvb2dsZS50bIIJZ29vZ2xl +LnRtgglnb29nbGUudG6CCWdvb2dsZS50b4IJZ29vZ2xlLnRwgglnb29nbGUudHSC +CWdvb2dsZS51c4IJZ29vZ2xlLnV6gglnb29nbGUudmeCCWdvb2dsZS52dYIJZ29v +Z2xlLndzMA0GCSqGSIb3DQEBBQUAA4GBAJmZ9RyqpUzrP0UcJnHXoLu/AjIEsIvZ +Y9hq/9bLry8InfmvERYHr4hNetkOYlW0FeDZtCpWxdPUgJjmWgKAK6j0goOFavTV +GptkL8gha4p1QUsdLkd36/cvBXeBYSle787veo46N1k4V6Uv2gaDVkre786CNsHv +Q6MYZ5ClQ+kS +-----END CERTIFICATE----- + diff --git a/test/netlib/data/text_cert_2 b/test/netlib/data/text_cert_2 new file mode 100644 index 00000000..ffe8faae --- /dev/null +++ b/test/netlib/data/text_cert_2 @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIGujCCBaKgAwIBAgIDAQlEMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ +TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0 +YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg +MSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTAwMTExMTkyNzM2 +WhcNMTEwMTEyMDkxNDU1WjCBtDEgMB4GA1UEDRMXMTI2ODMyLU1DeExzWTZUbjFn +bTdvOTAxCzAJBgNVBAYTAk5aMR4wHAYDVQQKExVQZXJzb25hIE5vdCBWYWxpZGF0 +ZWQxKTAnBgNVBAsTIFN0YXJ0Q29tIEZyZWUgQ2VydGlmaWNhdGUgTWVtYmVyMRgw +FgYDVQQDEw93d3cuaW5vZGUuY28ubnoxHjAcBgkqhkiG9w0BCQEWD2ppbUBpbm9k +ZS5jby5uejCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL6ghWlGhqg+ +V0P58R3SvLRiO9OrdekDxzmQbKwQcc05frnF5Z9vT6ga7YOuXVeXxhYCAo0nr6KI ++y/Lx+QHvP5W0nKbs+svzUQErq2ZZFwhh1e1LbVccrNwkHUzKOq0TTaVdU4k8kDQ +zzYF9tTZb+G5Hv1BJjpwYwe8P4cAiPJPrFFOKTySzHqiYsXlx+vR1l1e3zKavhd+ +LVSoLWWXb13yKODq6vnuiHjUJXl8CfVlBhoGotXU4JR5cbuGoW/8+rkwEdX+YoCv +VCqgdx9IkRFB6uWfN6ocUiFvhA0eknO+ewuVfRLiIaSDB8pNyUWVqu4ngFWtWO1O +YZg0I/32BkcCAwEAAaOCAvkwggL1MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgOoMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMB0GA1UdDgQWBBQfaL2Rj6r8iRlBTgppgE7ZZ5WT +UzAfBgNVHSMEGDAWgBTrQjTQmLCrn/Qbawj3zGQu7w4sRTAnBgNVHREEIDAegg93 +d3cuaW5vZGUuY28ubnqCC2lub2RlLmNvLm56MIIBQgYDVR0gBIIBOTCCATUwggEx +BgsrBgEEAYG1NwECATCCASAwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRz +c2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRz +c2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgbcGCCsGAQUFBwICMIGqMBQWDVN0YXJ0 +Q29tIEx0ZC4wAwIBARqBkUxpbWl0ZWQgTGlhYmlsaXR5LCBzZWUgc2VjdGlvbiAq +TGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRz +c2wuY29tL3BvbGljeS5wZGYwYQYDVR0fBFowWDAqoCigJoYkaHR0cDovL3d3dy5z +dGFydHNzbC5jb20vY3J0MS1jcmwuY3JsMCqgKKAmhiRodHRwOi8vY3JsLnN0YXJ0 +c3NsLmNvbS9jcnQxLWNybC5jcmwwgY4GCCsGAQUFBwEBBIGBMH8wOQYIKwYBBQUH +MAGGLWh0dHA6Ly9vY3NwLnN0YXJ0c3NsLmNvbS9zdWIvY2xhc3MxL3NlcnZlci9j +YTBCBggrBgEFBQcwAoY2aHR0cDovL3d3dy5zdGFydHNzbC5jb20vY2VydHMvc3Vi +LmNsYXNzMS5zZXJ2ZXIuY2EuY3J0MCMGA1UdEgQcMBqGGGh0dHA6Ly93d3cuc3Rh +cnRzc2wuY29tLzANBgkqhkiG9w0BAQUFAAOCAQEAivWID0KT8q1EzWzy+BecsFry +hQhuLFfAsPkHqpNd9OfkRStGBuJlLX+9DQ9TzjqutdY2buNBuDn71buZK+Y5fmjr +28rAT6+WMd+KnCl5WLT5IOS6Z9s3cec5TFQbmOGlepSS9Q6Ts9KsXOHHQvDkQeDq +OV2UqdgXIAyFm5efSL9JXPXntRausNu2s8F2B2rRJe4jPfnUy2LvY8OW1YvjUA++ +vpdWRdfUbJQp55mRfaYMPRnyUm30lAI27QaxgQPFOqDeZUm5llb5eFG/B3f87uhg ++Y1oEykbEvZrIFN4hithioQ0tb+57FKkkG2sW3uemNiQw2qrEo/GAMb1cI50Rg== +-----END CERTIFICATE----- + diff --git a/test/netlib/data/text_cert_weird1 b/test/netlib/data/text_cert_weird1 new file mode 100644 index 00000000..72b09dcb --- /dev/null +++ b/test/netlib/data/text_cert_weird1 @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFNDCCBBygAwIBAgIEDFJFNzANBgkqhkiG9w0BAQUFADCBjDELMAkGA1UEBhMC +REUxHjAcBgNVBAoTFVVuaXZlcnNpdGFldCBNdWVuc3RlcjE6MDgGA1UEAxMxWmVy +dGlmaXppZXJ1bmdzc3RlbGxlIFVuaXZlcnNpdGFldCBNdWVuc3RlciAtIEcwMjEh +MB8GCSqGSIb3DQEJARYSY2FAdW5pLW11ZW5zdGVyLmRlMB4XDTA4MDUyMDEyNDQy +NFoXDTEzMDUxOTEyNDQyNFowezELMAkGA1UEBhMCREUxHjAcBgNVBAoTFVVuaXZl +cnNpdGFldCBNdWVuc3RlcjEuMCwGA1UECxMlWmVudHJ1bSBmdWVyIEluZm9ybWF0 +aW9uc3ZlcmFyYmVpdHVuZzEcMBoGA1UEAxMTd3d3LnVuaS1tdWVuc3Rlci5kZTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMM0WlCj0ew+tyZ1GurBOqFn +AlChKk4S1F9oDzvp3FwOON4H8YFET7p9ZnoWtkfXSlGNMjekqy67dFlLt1sLusSo +tjNdaOrDLYmnGEgnYAT0RFBvErzIybJoD/Vu3NXyhes+L94R9mEMCwYXmSvG51H9 +c5CvguXBofMchDLCM/U6AYpwu3sST5orV3S1Rsa9sndj8sKJAcw195PYwl6EiEBb +M36ltDBlTYEUAg3Z+VSzB09J3U4vSvguVkDCz+szZh5RG3xlN9mlNfzhf4lHrNgV +0BRbKypa5Uuf81wbMcMMqTxKq+A9ysObpn9J3pNUym+Tn2oqHzGgvwZYB4tzXqUC +AwEAAaOCAawwggGoMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMBMGA1UdJQQMMAoG +CCsGAQUFBwMBMB0GA1UdDgQWBBQ3RFo8awewUTq5TpOFf3jOCEKihzAfBgNVHSME +GDAWgBS+nlGiyZJ8u2CL5rBoZHdaUhmhADAjBgNVHREEHDAagRh3d3dhZG1pbkB1 +bmktbXVlbnN0ZXIuZGUwewYDVR0fBHQwcjA3oDWgM4YxaHR0cDovL2NkcDEucGNh +LmRmbi5kZS93d3UtY2EvcHViL2NybC9nX2NhY3JsLmNybDA3oDWgM4YxaHR0cDov +L2NkcDIucGNhLmRmbi5kZS93d3UtY2EvcHViL2NybC9nX2NhY3JsLmNybDCBlgYI +KwYBBQUHAQEEgYkwgYYwQQYIKwYBBQUHMAKGNWh0dHA6Ly9jZHAxLnBjYS5kZm4u +ZGUvd3d1LWNhL3B1Yi9jYWNlcnQvZ19jYWNlcnQuY3J0MEEGCCsGAQUFBzAChjVo +dHRwOi8vY2RwMi5wY2EuZGZuLmRlL3d3dS1jYS9wdWIvY2FjZXJ0L2dfY2FjZXJ0 +LmNydDANBgkqhkiG9w0BAQUFAAOCAQEAFfNpagtcKUSDKss7TcqjYn99FQ4FtWjE +pGmzYL2zX2wsdCGoVQlGkieL9slbQVEUAnBuqM1LPzUNNe9kZpOPV3Rdhq4y8vyS +xkx3G1v5aGxfPUe8KM8yKIOHRqYefNronHJM0fw7KyjQ73xgbIEgkW+kNXaMLcrb +EPC36O2Zna8GP9FQxJRLgcfQCcYdRKGVn0EtRSkz2ym5Rbh/hrmJBbbC2yJGGMI0 +Vu5A9piK0EZPekZIUmhMQynD9QcMfWhTEFr7YZfx9ktxKDW4spnu7YrgICfZNcCm +tfxmnEAFt6a47u9P0w9lpY8+Sx9MNFfTePym+HP4TYha9bIBes+XnA== +-----END CERTIFICATE----- + diff --git a/test/netlib/data/verificationcerts/9da13359.0 b/test/netlib/data/verificationcerts/9da13359.0 new file mode 100644 index 00000000..b22e4d20 --- /dev/null +++ b/test/netlib/data/verificationcerts/9da13359.0 @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAPAfPQGCV/Z4MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTUxMTAxMTY0ODAxWhcNMTgwODIxMTY0ODAxWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArp8LD34JhKCwcQbwIYQMg4+eCgLVN8fwB7+/qOfJbArPs0djFBN+F7c6 +HGvMr24BKUk5u8pn4dPtNurm/vPC8ovNGmcXz62BQJpcMX2veVdRsF7yNwhNacNJ +Arq+70zNMwYBznx0XUxMF6j6nVFf3AW6SU04ylT4Mp3SY/BUUDAdfl1eRo0mPLNS +8rpsN+8YBw1Q7SCuBRVqpOgVIsL88svgQUSOlzvMZPBpG/cmB3BNKNrltwb5iFEI +1jAV7uSj5IcIuNO/246kfsDVPTFMJIzav/CUoidd5UNw+SoFDlzh8sA7L1Bm7D1/ +3KHYSKswGsSR3kynAl10w/SJKDtn8wIDAQABo1AwTjAdBgNVHQ4EFgQUgOcrtxBX +LxbpnOT65d+vpfyWUkgwHwYDVR0jBBgwFoAUgOcrtxBXLxbpnOT65d+vpfyWUkgw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEE9bFmUCA+6cvESKPoi2 +TGSpV652d0xd2U66LpEXeiWRJFLz8YGgoJCx3QFGBscJDXxrLxrBBBV/tCpEqypo +pYIqsawH7M66jpOr83Us3M8JC2eFBZJocMpXxdytWqHik5VKZNx6VQFT8bS7+yVC +VoUKePhlgcg+pmo41qjqieBNKRMh/1tXS77DI1lgO5wZLVrLXcdqWuDpmaQOKJeq +G/nxytCW/YJA7bFn/8Gjy8DYypJSeeaKu7o3P3+ONJHdIMHb+MdcheDBS9AOFSeo +xI0D5EbO9F873O77l7nbD7B0X34HFN0nGczC4poexIpbDFG3hAPekwZ5KC6VwJLc +1Q== +-----END CERTIFICATE----- diff --git a/test/netlib/data/verificationcerts/generate.py b/test/netlib/data/verificationcerts/generate.py new file mode 100644 index 00000000..9203abbb --- /dev/null +++ b/test/netlib/data/verificationcerts/generate.py @@ -0,0 +1,68 @@ +""" +Generate SSL test certificates. +""" +import subprocess +import shlex +import os +import shutil + + +ROOT_CA = "trusted-root" +SUBJECT = "/CN=example.mitmproxy.org/" + + +def do(args): + print("> %s" % args) + args = shlex.split(args) + output = subprocess.check_output(args) + return output + + +def genrsa(cert): + do("openssl genrsa -out {cert}.key 2048".format(cert=cert)) + + +def sign(cert): + do("openssl x509 -req -in {cert}.csr " + "-CA {root_ca}.crt " + "-CAkey {root_ca}.key " + "-CAcreateserial " + "-days 1024 " + "-out {cert}.crt".format(root_ca=ROOT_CA, cert=cert) + ) + + +def mkcert(cert, args): + genrsa(cert) + do("openssl req -new -nodes -batch " + "-key {cert}.key " + "{args} " + "-out {cert}.csr".format(cert=cert, args=args) + ) + sign(cert) + os.remove("{cert}.csr".format(cert=cert)) + + +# create trusted root CA +genrsa("trusted-root") +do("openssl req -x509 -new -nodes -batch " + "-key trusted-root.key " + "-days 1024 " + "-out trusted-root.crt" + ) +h = do("openssl x509 -hash -noout -in trusted-root.crt").decode("ascii").strip() +shutil.copyfile("trusted-root.crt", "{}.0".format(h)) + +# create trusted leaf cert. +mkcert("trusted-leaf", "-subj {}".format(SUBJECT)) + +# create self-signed cert +genrsa("self-signed") +do("openssl req -x509 -new -nodes -batch " + "-key self-signed.key " + "-subj {} " + "-days 1024 " + "-out self-signed.crt".format(SUBJECT) + ) + + diff --git a/test/netlib/data/verificationcerts/self-signed.crt b/test/netlib/data/verificationcerts/self-signed.crt new file mode 100644 index 00000000..dce2a7e0 --- /dev/null +++ b/test/netlib/data/verificationcerts/self-signed.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDEzCCAfugAwIBAgIJAJ945xt1FRsfMA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNV +BAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzAeFw0xNTExMDExNjQ4MDJaFw0xODA4 +MjExNjQ4MDJaMCAxHjAcBgNVBAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALFxyzPfjgIghOMMnJlW80yB84xC +nJtko3tuyOdozgTCyha2W+NdIKPNZJtWrzN4P0B5PlozCDwfcSYffLs0WZs8LRWv +BfZX8+oX+14qQjKFsiqgO65cTLP3qlPySYPJQQ37vOP1Y5Yf8nQq2mwQdC18hLtT +QOANG6OFoSplpBLsYF+QeoMgqCTa6hrl/5GLmQoDRTjXkv3Sj379AUDMybuBqccm +q5EIqCrE4+xJ8JywJclAVn2YP14baiFrrYCsYYg4sS1Od6xFj+xtpLe7My3AYjB9 +/aeHd8vDiob0cqOW1TFwhqgJKuErfFyg8lZ2hJmStJKyfofWuY/gl/vnvX0CAwEA +AaNQME4wHQYDVR0OBBYEFB8d32zK8eqZIoKw4jXzYzhw4amPMB8GA1UdIwQYMBaA +FB8d32zK8eqZIoKw4jXzYzhw4amPMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAJmo2oKv1OEjZ0Q4yELO6BAnHAkmBKpW+zmLyQa8idxtLVkI9uXk3iqY +GWugkmcUZCTVFRWv/QXQQSex+00IY3x2rdHbtuZwcyKiz2u8WEmfW1rOIwBaFJ1i +v7+SA2aZs6vepN2sE56X54c/YbwQooaKZtOb+djWXYMJrc/Ezj0J7oQIJTptYV8v +/3216yCHRp/KCL7yTLtiw25xKuXNu/gkcd8wZOY9rS2qMUD897MJF0MvgJoauRBd +d4XEYCNKkrIRmfqrkiRQfAZpvpoutH6NCk7KuQYcI0BlOHlsnHHcs/w72EEqHwFq +x6476tW/t8GJDZVD74+pNBcLifXxArE= +-----END CERTIFICATE----- diff --git a/test/netlib/data/verificationcerts/self-signed.key b/test/netlib/data/verificationcerts/self-signed.key new file mode 100644 index 00000000..71a6ad6a --- /dev/null +++ b/test/netlib/data/verificationcerts/self-signed.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsXHLM9+OAiCE4wycmVbzTIHzjEKcm2Sje27I52jOBMLKFrZb +410go81km1avM3g/QHk+WjMIPB9xJh98uzRZmzwtFa8F9lfz6hf7XipCMoWyKqA7 +rlxMs/eqU/JJg8lBDfu84/Vjlh/ydCrabBB0LXyEu1NA4A0bo4WhKmWkEuxgX5B6 +gyCoJNrqGuX/kYuZCgNFONeS/dKPfv0BQMzJu4GpxyarkQioKsTj7EnwnLAlyUBW +fZg/XhtqIWutgKxhiDixLU53rEWP7G2kt7szLcBiMH39p4d3y8OKhvRyo5bVMXCG +qAkq4St8XKDyVnaEmZK0krJ+h9a5j+CX++e9fQIDAQABAoIBAQCT+FvGbych2PJX +0D2KlXqgE0IAdc/YuYymstSwPLKIP9N8KyfnKtK8Jdw+uYOyfRTp8/EuEJ5OXL3j +V6CRD++lRwIlseVb7y5EySjh9oVrUhgn+aSrGucPsHkGNeZeEmbAfWugARLBrvRl +MRMhyHrJL6wT9jIEZInmy9mA3G99IuFW3rS8UR1Yu7zyvhtjvop1xg/wfEUu24Ty +PvMfnwaDcZHCz2tmu2KJvaxSBAG3FKmAqeMvk1Gt5m2keKgw03M+EX0LrM8ybWqn +VwB8tnSyMBLVFLIXMpIiSfpji10+p9fdKFMRF++D6qVwyoxPiIq+yEJapxXiqLea +mkhtJW91AoGBAOvIb7bZvH4wYvi6txs2pygF3ZMjqg/fycnplrmYMrjeeDeeN4v1 +h/5tkN9TeTkHRaN3L7v49NEUDhDyuopLTNfWpYdv63U/BVzvgMm/guacTYkx9whB +OvQ2YekR/WKg7kuyrTZidTDz+mjU+1b8JaWGjiDc6vFwxZA7uWicaGGHAoGBAMCo +y/2AwFGwCR+5bET1nTTyxok6iKo4k6R/7DJe4Bq8VLifoyX3zDlGG/33KN3xVqBU +xnT9gkii1lfX2U+4iM+GOSPl0nG0hOEqEH+vFHszpHybDeNez3FEyIbgOzg6u7sV +NOy+P94L5EMQVEmWp5g6Vm3k9kr92Bd9UacKQPnbAoGAMN8KyMu41i8RVJze9zUM +0K7mjmkGBuRL3x4br7xsRwVVxbF1sfzig0oSjTewGLH5LTi3HC8uD2gowjqNj7yr +4NEM3lXEaDj305uRBkA70bD0IUvJ+FwM7DGZecXQz3Cr8+TFIlCmGc94R+Jddlot +M3IAY69mw0SsroiylYxV1mECgYAcSGtx8rXJCDO+sYTgdsI2ZLGasbogax/ZlWIC +XwU9R4qUc/MKft8/RTiUxvT76BMUhH2B7Tl0GlunF6vyVR/Yf1biGzoSsTKUr40u +gXBbSdCK7mRSjbecZEGf80keTxkCNPHJE4DiwxImej41c2V1JpNLnMI/bhaMFDyp +bgrt4wKBgHFzZgAgM1v07F038tAkIBGrYLukY1ZFBaZoGZ9xHfy/EmLJM3HCHLO5 +8wszMGhMTe2+39EeChwgj0kFaq1YnDiucU74BC57KR1tD59y7l6UnsQXTm4/32j8 +Or6i8GekBibCb97DzzOU0ZK//fNhHTXpDDXsYt5lJUWSmgW+S9Qp +-----END RSA PRIVATE KEY----- diff --git a/test/netlib/data/verificationcerts/trusted-leaf.crt b/test/netlib/data/verificationcerts/trusted-leaf.crt new file mode 100644 index 00000000..6a92de92 --- /dev/null +++ b/test/netlib/data/verificationcerts/trusted-leaf.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC4TCCAckCCQCj6D9oVylb8jANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE1MTEwMTE2NDgwMloXDTE4MDgyMTE2NDgwMlowIDEeMBwG +A1UEAwwVZXhhbXBsZS5taXRtcHJveHkub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAy/L5JYHS7QFhSIsjmd6bJTgs2rdqEn6tsmPBVZKZ7SqCAVjW +hPpEu7Q23akmU6Zm9Fp/vENc3jzxQLlEKhrv7eWmFYSOrCYtbJOz3RQorlwjjfdY +LlNQh1wYUXQX3PN3r3dyYtt5vTtXKc8+aP4M4vX7qlbW+4j4LrQfmPjS0XOdYpu3 +wh+i1ZMIhZye3hpCjwnpjTf7/ff45ZFxtkoi1uzEC/+swr1RSvamY8Foe12Re17Z +5ij8ZB0NIdoSk1tDkY3sJ8iNi35+qartl0UYeG9IUXRwDRrPsEKpF4RxY1+X2bdZ +r6PKb/E4CA5JlMvS5SVmrvxjCVqTQBmTjXfxqwIDAQABMA0GCSqGSIb3DQEBCwUA +A4IBAQBmpSZJrTDvzSlo6P7P7x1LoETzHyVjwgPeqGYw6ndGXeJMN9rhhsFvRsiB +I/aHh58MIlSjti7paikDAoFHB3dBvFHR+JUa/ailWEbcZReWRSE3lV6wFiN3G3lU +OyofR7MKnPW7bv8hSqOLqP1mbupXuQFB5M6vPLRwg5VgiCHI/XBiTvzMamzvNAR3 +UHHZtsJkRqzogYm6K9YJaga7jteSx2nNo+ujLwrxeXsLChTyFMJGnVkp5IyKeNfc +qwlzNncb3y+4KnUdNkPEtuydgAxAfuyXufiFBYRcUWbQ5/9ycgF7131ySaj9f/Y2 +kMsv2jg+soKvwwVYCABsk1KSHtfz +-----END CERTIFICATE----- diff --git a/test/netlib/data/verificationcerts/trusted-leaf.key b/test/netlib/data/verificationcerts/trusted-leaf.key new file mode 100644 index 00000000..783ebf1c --- /dev/null +++ b/test/netlib/data/verificationcerts/trusted-leaf.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAy/L5JYHS7QFhSIsjmd6bJTgs2rdqEn6tsmPBVZKZ7SqCAVjW +hPpEu7Q23akmU6Zm9Fp/vENc3jzxQLlEKhrv7eWmFYSOrCYtbJOz3RQorlwjjfdY +LlNQh1wYUXQX3PN3r3dyYtt5vTtXKc8+aP4M4vX7qlbW+4j4LrQfmPjS0XOdYpu3 +wh+i1ZMIhZye3hpCjwnpjTf7/ff45ZFxtkoi1uzEC/+swr1RSvamY8Foe12Re17Z +5ij8ZB0NIdoSk1tDkY3sJ8iNi35+qartl0UYeG9IUXRwDRrPsEKpF4RxY1+X2bdZ +r6PKb/E4CA5JlMvS5SVmrvxjCVqTQBmTjXfxqwIDAQABAoIBAQC956DWq+wbhA1x +3x1nSUBth8E8Z0z9q7dRRFHhvIBXth0X5ADcEa2umj/8ZmSpv2heX2ZRhugSh+yc +t+YgzrRacFwV7ThsU6A4WdBBK2Q19tWke4xAlpOFdtut/Mu7kXkAidiY9ISHD5o5 +9B/I48ZcD3AnTHUiAogV9OL3LbogDD4HasLt4mWkbq8U2thdjxMIvxdg36olJEuo +iAZrAUCPZEXuU89BtvPLUYioe9n90nzkyneGNS0SHxotlEc9ZYK9VTsivtXJb4wB +ptDMCp+TH3tjo8BTGnbnoZEybgyyOEd0UTzxK4DlxnvRVWexFY6NXwPFhIxKlB0Y +Bg8NkAkBAoGBAOiRnmbC5QkqrKrTkLx3fghIHPqgEXPPYgHLSuY3UjTlMb3APXpq +vzQnlCn3QuSse/1fWnQj+9vLVbx1XNgKjzk7dQhn5IUY+mGN4lLmoSnTebxvSQ43 +VAgTYjST9JFmJ3wK4KkWDsEsVao8LAx0h5JEQXUTT5xZpFA2MLztYbgfAoGBAOB/ +MvhLMAwlx8+m/zXMEPLk/KOd2dVZ4q5se8bAT/GiGsi8JUcPnCk140ZZabJqryAp +JFzUHIjfVsS9ejAfocDk1JeIm7Uus4um6fQEKIPMBxI/M/UAwYCXAG9ULXqilbO3 +pTdeeuraVKrTu1Z4ea6x4du1JWKcyDfYfsHepcT1AoGBAM2fskV5G7e3G2MOG3IG +1E/OMpEE5WlXenfLnjVdxDkwS4JRbgnGR7d9JurTyzkTp6ylmfwFtLDoXq15ttTs +wSUBBMCh2tIy+201XV2eu++XIpMQca84C/v352RFTH8hqtdpZqkY74KsCDGzcd6x +SQxxfM5efIzoVPb2crEX0MZRAoGAQ2EqFSfL9flo7UQ8GRN0itJ7mUgJV2WxCZT5 +2X9i/y0eSN1feuKOhjfsTPMNLEWk5kwy48GuBs6xpj8Qa10zGUgVHp4bzdeEgAfK +9DhDSLt1694YZBKkAUpRERj8xXAC6nvWFLZAwjhhbRw7gAqMywgMt/q4i85usYRD +F0ESE/kCgYBbc083PcLmlHbkn/d1i4IcLI6wFk+tZYIEVYDid7xDOgZOBcOTTyYB +BrDzNqbKNexKRt7QHVlwR+VOGMdN5P0hf7oH3SMW23OxBKoQe8pUSGF9a4DjCS1v +vCXMekifb9kIhhUWaG71L8+MaOzNBVAmk1+3NzPZgV/YxHjAWWhGHQ== +-----END RSA PRIVATE KEY----- diff --git a/test/netlib/data/verificationcerts/trusted-root.crt b/test/netlib/data/verificationcerts/trusted-root.crt new file mode 100644 index 00000000..b22e4d20 --- /dev/null +++ b/test/netlib/data/verificationcerts/trusted-root.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAPAfPQGCV/Z4MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTUxMTAxMTY0ODAxWhcNMTgwODIxMTY0ODAxWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArp8LD34JhKCwcQbwIYQMg4+eCgLVN8fwB7+/qOfJbArPs0djFBN+F7c6 +HGvMr24BKUk5u8pn4dPtNurm/vPC8ovNGmcXz62BQJpcMX2veVdRsF7yNwhNacNJ +Arq+70zNMwYBznx0XUxMF6j6nVFf3AW6SU04ylT4Mp3SY/BUUDAdfl1eRo0mPLNS +8rpsN+8YBw1Q7SCuBRVqpOgVIsL88svgQUSOlzvMZPBpG/cmB3BNKNrltwb5iFEI +1jAV7uSj5IcIuNO/246kfsDVPTFMJIzav/CUoidd5UNw+SoFDlzh8sA7L1Bm7D1/ +3KHYSKswGsSR3kynAl10w/SJKDtn8wIDAQABo1AwTjAdBgNVHQ4EFgQUgOcrtxBX +LxbpnOT65d+vpfyWUkgwHwYDVR0jBBgwFoAUgOcrtxBXLxbpnOT65d+vpfyWUkgw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEE9bFmUCA+6cvESKPoi2 +TGSpV652d0xd2U66LpEXeiWRJFLz8YGgoJCx3QFGBscJDXxrLxrBBBV/tCpEqypo +pYIqsawH7M66jpOr83Us3M8JC2eFBZJocMpXxdytWqHik5VKZNx6VQFT8bS7+yVC +VoUKePhlgcg+pmo41qjqieBNKRMh/1tXS77DI1lgO5wZLVrLXcdqWuDpmaQOKJeq +G/nxytCW/YJA7bFn/8Gjy8DYypJSeeaKu7o3P3+ONJHdIMHb+MdcheDBS9AOFSeo +xI0D5EbO9F873O77l7nbD7B0X34HFN0nGczC4poexIpbDFG3hAPekwZ5KC6VwJLc +1Q== +-----END CERTIFICATE----- diff --git a/test/netlib/data/verificationcerts/trusted-root.key b/test/netlib/data/verificationcerts/trusted-root.key new file mode 100644 index 00000000..05483f77 --- /dev/null +++ b/test/netlib/data/verificationcerts/trusted-root.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEArp8LD34JhKCwcQbwIYQMg4+eCgLVN8fwB7+/qOfJbArPs0dj +FBN+F7c6HGvMr24BKUk5u8pn4dPtNurm/vPC8ovNGmcXz62BQJpcMX2veVdRsF7y +NwhNacNJArq+70zNMwYBznx0XUxMF6j6nVFf3AW6SU04ylT4Mp3SY/BUUDAdfl1e +Ro0mPLNS8rpsN+8YBw1Q7SCuBRVqpOgVIsL88svgQUSOlzvMZPBpG/cmB3BNKNrl +twb5iFEI1jAV7uSj5IcIuNO/246kfsDVPTFMJIzav/CUoidd5UNw+SoFDlzh8sA7 +L1Bm7D1/3KHYSKswGsSR3kynAl10w/SJKDtn8wIDAQABAoIBAFgMzjDzpqz/sbhs +fS0JPp4gDtqRbx3/bSMbJvNuXPxjvzNxLZ5z7cLbmyu1l7Jlz6QXzkrI1vTiPdzR +OcUY+RYANF252iHYJTKEIzS5YX/X7dL3LT9eqlpIJEqCC8Dygw3VW5fY3Xwl+sB7 +blNhMuro4HQRwi8UBUrQlcPa7Ui5BBi323Q6en+VjYctkqpJHzNKPSqPTbsdLaK+ +B0XuXxFatM09rmeRKZCL71Lk1T8N/l0hqEzej7zxgVD7vG/x1kMFN4T3yCmXCbPa +izGHYr1EBHglm4qMNWveXCZiVJ+wmwCjdjqvggyHiZFXE2N0OCrWPhxQPdqFf5y7 +bUO9U2ECgYEA6GM1UzRnbVpjb20ezFy7dU7rlWM0nHBfG27M3bcXh4HnPpnvKp0/ +8a1WFi4kkRywrNXx8hFEd43vTbdObLpVXScXRKiY3MHmFk4k4hbWuTpmumCubQZO +AWlX6TE0HRKn1wQahgpQcxcWaDN2xJJmRQ1zVmlnNkT48/4kFgRxyykCgYEAwF08 +ngrF35oYoU/x+KKq2NXGeNUzoZMj568dE1oWW0ZFpqCi+DGT+hAbG3yUOBSaPqy9 +zn1obGo0YRlrayvtebz118kG7a/rzY02VcAPlT/GpEhvkZlXTwEK17zRJc1nJrfP +39QAZWZsaOru9NRIg/8HcdG3JPR2MhRD/De9GbsCgYAaiZnBUq6s8jGAu/lUZRKT +JtwIRzfu1XZG77Q9bXcmZlM99t41A5gVxTGbftF2MMyMMDJc7lPfQzocqd4u1GiD +Jr+le4tZSls4GNxlZS5IIL8ycW/5y0qFJr5/RrsoxsSb7UAKJothWTWZ2Karc/xx +zkNpjsfWjrHPSypbyU4lYQKBgFh1R5/BgnatjO/5LGNSok/uFkOQfxqo6BTtYOh6 +P9efO/5A1lBdtBeE+oIsSphzWO7DTtE6uB9Kw2V3Y/83hw+5RjABoG8Cu+OdMURD +eqb+WeFH8g45Pn31E8Bbcq34g5u5YR0jhz8Z13ZzuojZabNRPmIntxmGVSf4S78a +/plrAoGBANMHNng2lyr03nqnHrOM6NXD+60af0YR/YJ+2d/H40RnXxGJ4DXn7F00 +a4vJFPa97uq+xpd0HE+TE+NIrOdVDXPePD2qzBzMTsctGtj30vLzojMOT+Yf/nvO +WxTL5Q8GruJz2Dn0awSZO2z/3A8S1rmpuVZ/jT5NtRrvOSY6hmxF +-----END RSA PRIVATE KEY----- diff --git a/test/netlib/data/verificationcerts/trusted-root.srl b/test/netlib/data/verificationcerts/trusted-root.srl new file mode 100644 index 00000000..4ad962ba --- /dev/null +++ b/test/netlib/data/verificationcerts/trusted-root.srl @@ -0,0 +1 @@ +A3E83F6857295BF2 diff --git a/test/netlib/http/__init__.py b/test/netlib/http/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/netlib/http/__init__.py diff --git a/test/netlib/http/http1/__init__.py b/test/netlib/http/http1/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/netlib/http/http1/__init__.py diff --git a/test/netlib/http/http1/test_assemble.py b/test/netlib/http/http1/test_assemble.py new file mode 100644 index 00000000..31a62438 --- /dev/null +++ b/test/netlib/http/http1/test_assemble.py @@ -0,0 +1,102 @@ +from __future__ import absolute_import, print_function, division +from netlib.exceptions import HttpException +from netlib.http import CONTENT_MISSING, Headers +from netlib.http.http1.assemble import ( + assemble_request, assemble_request_head, assemble_response, + assemble_response_head, _assemble_request_line, _assemble_request_headers, + _assemble_response_headers, + assemble_body) +from netlib.tutils import treq, raises, tresp + + +def test_assemble_request(): + c = assemble_request(treq()) == ( + b"GET /path HTTP/1.1\r\n" + b"header: qvalue\r\n" + b"Host: address:22\r\n" + b"Content-Length: 7\r\n" + b"\r\n" + b"content" + ) + + with raises(HttpException): + assemble_request(treq(content=CONTENT_MISSING)) + + +def test_assemble_request_head(): + c = assemble_request_head(treq(content="foo")) + assert b"GET" in c + assert b"qvalue" in c + assert b"content-length" in c + assert b"foo" not in c + + +def test_assemble_response(): + c = assemble_response(tresp()) == ( + b"HTTP/1.1 200 OK\r\n" + b"header-response: svalue\r\n" + b"Content-Length: 7\r\n" + b"\r\n" + b"message" + ) + + with raises(HttpException): + assemble_response(tresp(content=CONTENT_MISSING)) + + +def test_assemble_response_head(): + c = assemble_response_head(tresp()) + assert b"200" in c + assert b"svalue" in c + assert b"message" not in c + + +def test_assemble_body(): + c = list(assemble_body(Headers(), [b"body"])) + assert c == [b"body"] + + c = list(assemble_body(Headers(transfer_encoding="chunked"), [b"123456789a", b""])) + assert c == [b"a\r\n123456789a\r\n", b"0\r\n\r\n"] + + c = list(assemble_body(Headers(transfer_encoding="chunked"), [b"123456789a"])) + assert c == [b"a\r\n123456789a\r\n", b"0\r\n\r\n"] + + +def test_assemble_request_line(): + assert _assemble_request_line(treq().data) == b"GET /path HTTP/1.1" + + authority_request = treq(method=b"CONNECT", first_line_format="authority").data + assert _assemble_request_line(authority_request) == b"CONNECT address:22 HTTP/1.1" + + absolute_request = treq(first_line_format="absolute").data + assert _assemble_request_line(absolute_request) == b"GET http://address:22/path HTTP/1.1" + + with raises(RuntimeError): + _assemble_request_line(treq(first_line_format="invalid_form").data) + + +def test_assemble_request_headers(): + # https://github.com/mitmproxy/mitmproxy/issues/186 + r = treq(content=b"") + r.headers["Transfer-Encoding"] = "chunked" + c = _assemble_request_headers(r.data) + assert b"Transfer-Encoding" in c + + +def test_assemble_request_headers_host_header(): + r = treq() + r.headers = Headers() + c = _assemble_request_headers(r.data) + assert b"host" in c + + r.host = None + c = _assemble_request_headers(r.data) + assert b"host" not in c + + +def test_assemble_response_headers(): + # https://github.com/mitmproxy/mitmproxy/issues/186 + r = tresp(content=b"") + r.headers["Transfer-Encoding"] = "chunked" + c = _assemble_response_headers(r) + assert b"Transfer-Encoding" in c diff --git a/test/netlib/http/http1/test_read.py b/test/netlib/http/http1/test_read.py new file mode 100644 index 00000000..90234070 --- /dev/null +++ b/test/netlib/http/http1/test_read.py @@ -0,0 +1,333 @@ +from __future__ import absolute_import, print_function, division +from io import BytesIO +import textwrap +from mock import Mock +from netlib.exceptions import HttpException, HttpSyntaxException, HttpReadDisconnect, TcpDisconnect +from netlib.http import Headers +from netlib.http.http1.read import ( + read_request, read_response, read_request_head, + read_response_head, read_body, connection_close, expected_http_body_size, _get_first_line, + _read_request_line, _parse_authority_form, _read_response_line, _check_http_version, + _read_headers, _read_chunked +) +from netlib.tutils import treq, tresp, raises + + +def test_read_request(): + rfile = BytesIO(b"GET / HTTP/1.1\r\n\r\nskip") + r = read_request(rfile) + assert r.method == "GET" + assert r.content == b"" + assert r.timestamp_end + assert rfile.read() == b"skip" + + +def test_read_request_head(): + rfile = BytesIO( + b"GET / HTTP/1.1\r\n" + b"Content-Length: 4\r\n" + b"\r\n" + b"skip" + ) + rfile.reset_timestamps = Mock() + rfile.first_byte_timestamp = 42 + r = read_request_head(rfile) + assert r.method == "GET" + assert r.headers["Content-Length"] == "4" + assert r.content is None + assert rfile.reset_timestamps.called + assert r.timestamp_start == 42 + assert rfile.read() == b"skip" + + +def test_read_response(): + req = treq() + rfile = BytesIO(b"HTTP/1.1 418 I'm a teapot\r\n\r\nbody") + r = read_response(rfile, req) + assert r.status_code == 418 + assert r.content == b"body" + assert r.timestamp_end + + +def test_read_response_head(): + rfile = BytesIO( + b"HTTP/1.1 418 I'm a teapot\r\n" + b"Content-Length: 4\r\n" + b"\r\n" + b"skip" + ) + rfile.reset_timestamps = Mock() + rfile.first_byte_timestamp = 42 + r = read_response_head(rfile) + assert r.status_code == 418 + assert r.headers["Content-Length"] == "4" + assert r.content is None + assert rfile.reset_timestamps.called + assert r.timestamp_start == 42 + assert rfile.read() == b"skip" + + +class TestReadBody(object): + def test_chunked(self): + rfile = BytesIO(b"3\r\nfoo\r\n0\r\n\r\nbar") + body = b"".join(read_body(rfile, None)) + assert body == b"foo" + assert rfile.read() == b"bar" + + def test_known_size(self): + rfile = BytesIO(b"foobar") + body = b"".join(read_body(rfile, 3)) + assert body == b"foo" + assert rfile.read() == b"bar" + + def test_known_size_limit(self): + rfile = BytesIO(b"foobar") + with raises(HttpException): + b"".join(read_body(rfile, 3, 2)) + + def test_known_size_too_short(self): + rfile = BytesIO(b"foo") + with raises(HttpException): + b"".join(read_body(rfile, 6)) + + def test_unknown_size(self): + rfile = BytesIO(b"foobar") + body = b"".join(read_body(rfile, -1)) + assert body == b"foobar" + + def test_unknown_size_limit(self): + rfile = BytesIO(b"foobar") + with raises(HttpException): + b"".join(read_body(rfile, -1, 3)) + + def test_max_chunk_size(self): + rfile = BytesIO(b"123456") + assert list(read_body(rfile, -1, max_chunk_size=None)) == [b"123456"] + rfile = BytesIO(b"123456") + assert list(read_body(rfile, -1, max_chunk_size=1)) == [b"1", b"2", b"3", b"4", b"5", b"6"] + +def test_connection_close(): + headers = Headers() + assert connection_close(b"HTTP/1.0", headers) + assert not connection_close(b"HTTP/1.1", headers) + + headers["connection"] = "keep-alive" + assert not connection_close(b"HTTP/1.1", headers) + + headers["connection"] = "close" + assert connection_close(b"HTTP/1.1", headers) + + headers["connection"] = "foobar" + assert connection_close(b"HTTP/1.0", headers) + assert not connection_close(b"HTTP/1.1", headers) + +def test_expected_http_body_size(): + # Expect: 100-continue + assert expected_http_body_size( + treq(headers=Headers(expect="100-continue", content_length="42")) + ) == 0 + + # http://tools.ietf.org/html/rfc7230#section-3.3 + assert expected_http_body_size( + treq(method=b"HEAD"), + tresp(headers=Headers(content_length="42")) + ) == 0 + assert expected_http_body_size( + treq(method=b"CONNECT"), + tresp() + ) == 0 + for code in (100, 204, 304): + assert expected_http_body_size( + treq(), + tresp(status_code=code) + ) == 0 + + # chunked + assert expected_http_body_size( + treq(headers=Headers(transfer_encoding="chunked")), + ) is None + + # explicit length + for val in (b"foo", b"-7"): + with raises(HttpSyntaxException): + expected_http_body_size( + treq(headers=Headers(content_length=val)) + ) + assert expected_http_body_size( + treq(headers=Headers(content_length="42")) + ) == 42 + + # no length + assert expected_http_body_size( + treq(headers=Headers()) + ) == 0 + assert expected_http_body_size( + treq(headers=Headers()), tresp(headers=Headers()) + ) == -1 + + +def test_get_first_line(): + rfile = BytesIO(b"foo\r\nbar") + assert _get_first_line(rfile) == b"foo" + + rfile = BytesIO(b"\r\nfoo\r\nbar") + assert _get_first_line(rfile) == b"foo" + + with raises(HttpReadDisconnect): + rfile = BytesIO(b"") + _get_first_line(rfile) + + with raises(HttpReadDisconnect): + rfile = Mock() + rfile.readline.side_effect = TcpDisconnect + _get_first_line(rfile) + + +def test_read_request_line(): + def t(b): + return _read_request_line(BytesIO(b)) + + assert (t(b"GET / HTTP/1.1") == + ("relative", b"GET", None, None, None, b"/", b"HTTP/1.1")) + assert (t(b"OPTIONS * HTTP/1.1") == + ("relative", b"OPTIONS", None, None, None, b"*", b"HTTP/1.1")) + assert (t(b"CONNECT foo:42 HTTP/1.1") == + ("authority", b"CONNECT", None, b"foo", 42, None, b"HTTP/1.1")) + assert (t(b"GET http://foo:42/bar HTTP/1.1") == + ("absolute", b"GET", b"http", b"foo", 42, b"/bar", b"HTTP/1.1")) + + with raises(HttpSyntaxException): + t(b"GET / WTF/1.1") + with raises(HttpSyntaxException): + t(b"this is not http") + with raises(HttpReadDisconnect): + t(b"") + +def test_parse_authority_form(): + assert _parse_authority_form(b"foo:42") == (b"foo", 42) + with raises(HttpSyntaxException): + _parse_authority_form(b"foo") + with raises(HttpSyntaxException): + _parse_authority_form(b"foo:bar") + with raises(HttpSyntaxException): + _parse_authority_form(b"foo:99999999") + with raises(HttpSyntaxException): + _parse_authority_form(b"f\x00oo:80") + + +def test_read_response_line(): + def t(b): + return _read_response_line(BytesIO(b)) + + assert t(b"HTTP/1.1 200 OK") == (b"HTTP/1.1", 200, b"OK") + assert t(b"HTTP/1.1 200") == (b"HTTP/1.1", 200, b"") + + # https://github.com/mitmproxy/mitmproxy/issues/784 + assert t(b"HTTP/1.1 200 Non-Autoris\xc3\xa9") == (b"HTTP/1.1", 200, b"Non-Autoris\xc3\xa9") + + with raises(HttpSyntaxException): + assert t(b"HTTP/1.1") + + with raises(HttpSyntaxException): + t(b"HTTP/1.1 OK OK") + with raises(HttpSyntaxException): + t(b"WTF/1.1 200 OK") + with raises(HttpReadDisconnect): + t(b"") + + +def test_check_http_version(): + _check_http_version(b"HTTP/0.9") + _check_http_version(b"HTTP/1.0") + _check_http_version(b"HTTP/1.1") + _check_http_version(b"HTTP/2.0") + with raises(HttpSyntaxException): + _check_http_version(b"WTF/1.0") + with raises(HttpSyntaxException): + _check_http_version(b"HTTP/1.10") + with raises(HttpSyntaxException): + _check_http_version(b"HTTP/1.b") + + +class TestReadHeaders(object): + @staticmethod + def _read(data): + return _read_headers(BytesIO(data)) + + def test_read_simple(self): + data = ( + b"Header: one\r\n" + b"Header2: two\r\n" + b"\r\n" + ) + headers = self._read(data) + assert headers.fields == [[b"Header", b"one"], [b"Header2", b"two"]] + + def test_read_multi(self): + data = ( + b"Header: one\r\n" + b"Header: two\r\n" + b"\r\n" + ) + headers = self._read(data) + assert headers.fields == [[b"Header", b"one"], [b"Header", b"two"]] + + def test_read_continued(self): + data = ( + b"Header: one\r\n" + b"\ttwo\r\n" + b"Header2: three\r\n" + b"\r\n" + ) + headers = self._read(data) + assert headers.fields == [[b"Header", b"one\r\n two"], [b"Header2", b"three"]] + + def test_read_continued_err(self): + data = b"\tfoo: bar\r\n" + with raises(HttpSyntaxException): + self._read(data) + + def test_read_err(self): + data = b"foo" + with raises(HttpSyntaxException): + self._read(data) + + def test_read_empty_name(self): + data = b":foo" + with raises(HttpSyntaxException): + self._read(data) + + def test_read_empty_value(self): + data = b"bar:" + headers = self._read(data) + assert headers.fields == [[b"bar", b""]] + +def test_read_chunked(): + req = treq(content=None) + req.headers["Transfer-Encoding"] = "chunked" + + data = b"1\r\na\r\n0\r\n" + with raises(HttpSyntaxException): + b"".join(_read_chunked(BytesIO(data))) + + data = b"1\r\na\r\n0\r\n\r\n" + assert b"".join(_read_chunked(BytesIO(data))) == b"a" + + data = b"\r\n\r\n1\r\na\r\n1\r\nb\r\n0\r\n\r\n" + assert b"".join(_read_chunked(BytesIO(data))) == b"ab" + + data = b"\r\n" + with raises("closed prematurely"): + b"".join(_read_chunked(BytesIO(data))) + + data = b"1\r\nfoo" + with raises("malformed chunked body"): + b"".join(_read_chunked(BytesIO(data))) + + data = b"foo\r\nfoo" + with raises(HttpSyntaxException): + b"".join(_read_chunked(BytesIO(data))) + + data = b"5\r\naaaaa\r\n0\r\n\r\n" + with raises("too large"): + b"".join(_read_chunked(BytesIO(data), limit=2)) diff --git a/test/netlib/http/http2/__init__.py b/test/netlib/http/http2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/netlib/http/http2/__init__.py diff --git a/test/netlib/http/http2/test_connections.py b/test/netlib/http/http2/test_connections.py new file mode 100644 index 00000000..8be127e4 --- /dev/null +++ b/test/netlib/http/http2/test_connections.py @@ -0,0 +1,540 @@ +import OpenSSL +import mock +import codecs + +from hyperframe.frame import * + +from netlib import tcp, http, utils, tservers +from netlib.tutils import raises +from netlib.exceptions import TcpDisconnect +from netlib.http.http2.connections import HTTP2Protocol, TCPHandler + + +class TestTCPHandlerWrapper: + def test_wrapped(self): + h = TCPHandler(rfile='foo', wfile='bar') + p = HTTP2Protocol(h) + assert p.tcp_handler.rfile == 'foo' + assert p.tcp_handler.wfile == 'bar' + + def test_direct(self): + p = HTTP2Protocol(rfile='foo', wfile='bar') + assert isinstance(p.tcp_handler, TCPHandler) + assert p.tcp_handler.rfile == 'foo' + assert p.tcp_handler.wfile == 'bar' + + +class EchoHandler(tcp.BaseHandler): + sni = None + + def handle(self): + while True: + v = self.rfile.safe_read(1) + self.wfile.write(v) + self.wfile.flush() + + +class TestProtocol: + @mock.patch("netlib.http.http2.connections.HTTP2Protocol.perform_server_connection_preface") + @mock.patch("netlib.http.http2.connections.HTTP2Protocol.perform_client_connection_preface") + def test_perform_connection_preface(self, mock_client_method, mock_server_method): + protocol = HTTP2Protocol(is_server=False) + protocol.connection_preface_performed = True + + protocol.perform_connection_preface() + assert not mock_client_method.called + assert not mock_server_method.called + + protocol.perform_connection_preface(force=True) + assert mock_client_method.called + assert not mock_server_method.called + + @mock.patch("netlib.http.http2.connections.HTTP2Protocol.perform_server_connection_preface") + @mock.patch("netlib.http.http2.connections.HTTP2Protocol.perform_client_connection_preface") + def test_perform_connection_preface_server(self, mock_client_method, mock_server_method): + protocol = HTTP2Protocol(is_server=True) + protocol.connection_preface_performed = True + + protocol.perform_connection_preface() + assert not mock_client_method.called + assert not mock_server_method.called + + protocol.perform_connection_preface(force=True) + assert not mock_client_method.called + assert mock_server_method.called + + +class TestCheckALPNMatch(tservers.ServerTestBase): + handler = EchoHandler + ssl = dict( + alpn_select=b'h2', + ) + + if tcp.HAS_ALPN: + + def test_check_alpn(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl(alpn_protos=[b'h2']) + protocol = HTTP2Protocol(c) + assert protocol.check_alpn() + + +class TestCheckALPNMismatch(tservers.ServerTestBase): + handler = EchoHandler + ssl = dict( + alpn_select=None, + ) + + if tcp.HAS_ALPN: + + def test_check_alpn(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl(alpn_protos=[b'h2']) + protocol = HTTP2Protocol(c) + with raises(NotImplementedError): + protocol.check_alpn() + + +class TestPerformServerConnectionPreface(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + + def handle(self): + # send magic + self.wfile.write(codecs.decode('505249202a20485454502f322e300d0a0d0a534d0d0a0d0a', 'hex_codec')) + self.wfile.flush() + + # send empty settings frame + self.wfile.write(codecs.decode('000000040000000000', 'hex_codec')) + self.wfile.flush() + + # check empty settings frame + raw = utils.http2_read_raw_frame(self.rfile) + assert raw == codecs.decode('00000c040000000000000200000000000300000001', 'hex_codec') + + # check settings acknowledgement + raw = utils.http2_read_raw_frame(self.rfile) + assert raw == codecs.decode('000000040100000000', 'hex_codec') + + # send settings acknowledgement + self.wfile.write(codecs.decode('000000040100000000', 'hex_codec')) + self.wfile.flush() + + def test_perform_server_connection_preface(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + protocol = HTTP2Protocol(c) + + assert not protocol.connection_preface_performed + protocol.perform_server_connection_preface() + assert protocol.connection_preface_performed + + with raises(TcpDisconnect): + protocol.perform_server_connection_preface(force=True) + + +class TestPerformClientConnectionPreface(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + + def handle(self): + # check magic + assert self.rfile.read(24) == HTTP2Protocol.CLIENT_CONNECTION_PREFACE + + # check empty settings frame + assert self.rfile.read(9) ==\ + codecs.decode('000000040000000000', 'hex_codec') + + # send empty settings frame + self.wfile.write(codecs.decode('000000040000000000', 'hex_codec')) + self.wfile.flush() + + # check settings acknowledgement + assert self.rfile.read(9) == \ + codecs.decode('000000040100000000', 'hex_codec') + + # send settings acknowledgement + self.wfile.write(codecs.decode('000000040100000000', 'hex_codec')) + self.wfile.flush() + + def test_perform_client_connection_preface(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + protocol = HTTP2Protocol(c) + + assert not protocol.connection_preface_performed + protocol.perform_client_connection_preface() + assert protocol.connection_preface_performed + + +class TestClientStreamIds(object): + c = tcp.TCPClient(("127.0.0.1", 0)) + protocol = HTTP2Protocol(c) + + def test_client_stream_ids(self): + assert self.protocol.current_stream_id is None + assert self.protocol._next_stream_id() == 1 + assert self.protocol.current_stream_id == 1 + assert self.protocol._next_stream_id() == 3 + assert self.protocol.current_stream_id == 3 + assert self.protocol._next_stream_id() == 5 + assert self.protocol.current_stream_id == 5 + + +class TestServerStreamIds(object): + c = tcp.TCPClient(("127.0.0.1", 0)) + protocol = HTTP2Protocol(c, is_server=True) + + def test_server_stream_ids(self): + assert self.protocol.current_stream_id is None + assert self.protocol._next_stream_id() == 2 + assert self.protocol.current_stream_id == 2 + assert self.protocol._next_stream_id() == 4 + assert self.protocol.current_stream_id == 4 + assert self.protocol._next_stream_id() == 6 + assert self.protocol.current_stream_id == 6 + + +class TestApplySettings(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + def handle(self): + # check settings acknowledgement + assert self.rfile.read(9) == codecs.decode('000000040100000000', 'hex_codec') + self.wfile.write("OK") + self.wfile.flush() + self.rfile.safe_read(9) # just to keep the connection alive a bit longer + + ssl = True + + def test_apply_settings(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + protocol = HTTP2Protocol(c) + + protocol._apply_settings({ + SettingsFrame.ENABLE_PUSH: 'foo', + SettingsFrame.MAX_CONCURRENT_STREAMS: 'bar', + SettingsFrame.INITIAL_WINDOW_SIZE: 'deadbeef', + }) + + assert c.rfile.safe_read(2) == b"OK" + + assert protocol.http2_settings[ + SettingsFrame.ENABLE_PUSH] == 'foo' + assert protocol.http2_settings[ + SettingsFrame.MAX_CONCURRENT_STREAMS] == 'bar' + assert protocol.http2_settings[ + SettingsFrame.INITIAL_WINDOW_SIZE] == 'deadbeef' + + +class TestCreateHeaders(object): + c = tcp.TCPClient(("127.0.0.1", 0)) + + def test_create_headers(self): + headers = http.Headers([ + (b':method', b'GET'), + (b':path', b'index.html'), + (b':scheme', b'https'), + (b'foo', b'bar')]) + + bytes = HTTP2Protocol(self.c)._create_headers( + headers, 1, end_stream=True) + assert b''.join(bytes) ==\ + codecs.decode('000014010500000001824488355217caf3a69a3f87408294e7838c767f', 'hex_codec') + + bytes = HTTP2Protocol(self.c)._create_headers( + headers, 1, end_stream=False) + assert b''.join(bytes) ==\ + codecs.decode('000014010400000001824488355217caf3a69a3f87408294e7838c767f', 'hex_codec') + + def test_create_headers_multiple_frames(self): + headers = http.Headers([ + (b':method', b'GET'), + (b':path', b'/'), + (b':scheme', b'https'), + (b'foo', b'bar'), + (b'server', b'version')]) + + protocol = HTTP2Protocol(self.c) + protocol.http2_settings[SettingsFrame.MAX_FRAME_SIZE] = 8 + bytes = protocol._create_headers(headers, 1, end_stream=True) + assert len(bytes) == 3 + assert bytes[0] == codecs.decode('000008010100000001828487408294e783', 'hex_codec') + assert bytes[1] == codecs.decode('0000080900000000018c767f7685ee5b10', 'hex_codec') + assert bytes[2] == codecs.decode('00000209040000000163d5', 'hex_codec') + + +class TestCreateBody(object): + c = tcp.TCPClient(("127.0.0.1", 0)) + + def test_create_body_empty(self): + protocol = HTTP2Protocol(self.c) + bytes = protocol._create_body(b'', 1) + assert b''.join(bytes) == b'' + + def test_create_body_single_frame(self): + protocol = HTTP2Protocol(self.c) + bytes = protocol._create_body(b'foobar', 1) + assert b''.join(bytes) == codecs.decode('000006000100000001666f6f626172', 'hex_codec') + + def test_create_body_multiple_frames(self): + protocol = HTTP2Protocol(self.c) + protocol.http2_settings[SettingsFrame.MAX_FRAME_SIZE] = 5 + bytes = protocol._create_body(b'foobarmehm42', 1) + assert len(bytes) == 3 + assert bytes[0] == codecs.decode('000005000000000001666f6f6261', 'hex_codec') + assert bytes[1] == codecs.decode('000005000000000001726d65686d', 'hex_codec') + assert bytes[2] == codecs.decode('0000020001000000013432', 'hex_codec') + + +class TestReadRequest(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + + def handle(self): + self.wfile.write( + codecs.decode('000003010400000001828487', 'hex_codec')) + self.wfile.write( + codecs.decode('000006000100000001666f6f626172', 'hex_codec')) + self.wfile.flush() + self.rfile.safe_read(9) # just to keep the connection alive a bit longer + + ssl = True + + def test_read_request(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + protocol = HTTP2Protocol(c, is_server=True) + protocol.connection_preface_performed = True + + req = protocol.read_request(NotImplemented) + + assert req.stream_id + assert req.headers.fields == [[b':method', b'GET'], [b':path', b'/'], [b':scheme', b'https']] + assert req.content == b'foobar' + + +class TestReadRequestRelative(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + def handle(self): + self.wfile.write( + codecs.decode('00000c0105000000014287d5af7e4d5a777f4481f9', 'hex_codec')) + self.wfile.flush() + + ssl = True + + def test_asterisk_form_in(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + protocol = HTTP2Protocol(c, is_server=True) + protocol.connection_preface_performed = True + + req = protocol.read_request(NotImplemented) + + assert req.form_in == "relative" + assert req.method == "OPTIONS" + assert req.path == "*" + + +class TestReadRequestAbsolute(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + def handle(self): + self.wfile.write( + codecs.decode('00001901050000000182448d9d29aee30c0e492c2a1170426366871c92585422e085', 'hex_codec')) + self.wfile.flush() + + ssl = True + + def test_absolute_form_in(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + protocol = HTTP2Protocol(c, is_server=True) + protocol.connection_preface_performed = True + + req = protocol.read_request(NotImplemented) + + assert req.form_in == "absolute" + assert req.scheme == "http" + assert req.host == "address" + assert req.port == 22 + + +class TestReadRequestConnect(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + def handle(self): + self.wfile.write( + codecs.decode('00001b0105000000014287bdab4e9c17b7ff44871c92585422e08541871c92585422e085', 'hex_codec')) + self.wfile.write( + codecs.decode('00001d0105000000014287bdab4e9c17b7ff44882f91d35d055c87a741882f91d35d055c87a7', 'hex_codec')) + self.wfile.flush() + + ssl = True + + def test_connect(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + protocol = HTTP2Protocol(c, is_server=True) + protocol.connection_preface_performed = True + + req = protocol.read_request(NotImplemented) + assert req.form_in == "authority" + assert req.method == "CONNECT" + assert req.host == "address" + assert req.port == 22 + + req = protocol.read_request(NotImplemented) + assert req.form_in == "authority" + assert req.method == "CONNECT" + assert req.host == "example.com" + assert req.port == 443 + + +class TestReadResponse(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + def handle(self): + self.wfile.write( + codecs.decode('00000801040000002a88628594e78c767f', 'hex_codec')) + self.wfile.write( + codecs.decode('00000600010000002a666f6f626172', 'hex_codec')) + self.wfile.flush() + self.rfile.safe_read(9) # just to keep the connection alive a bit longer + + ssl = True + + def test_read_response(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + protocol = HTTP2Protocol(c) + protocol.connection_preface_performed = True + + resp = protocol.read_response(NotImplemented, stream_id=42) + + assert resp.http_version == "HTTP/2.0" + assert resp.status_code == 200 + assert resp.msg == '' + assert resp.headers.fields == [[b':status', b'200'], [b'etag', b'foobar']] + assert resp.content == b'foobar' + assert resp.timestamp_end + + +class TestReadEmptyResponse(tservers.ServerTestBase): + class handler(tcp.BaseHandler): + def handle(self): + self.wfile.write( + codecs.decode('00000801050000002a88628594e78c767f', 'hex_codec')) + self.wfile.flush() + + ssl = True + + def test_read_empty_response(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + protocol = HTTP2Protocol(c) + protocol.connection_preface_performed = True + + resp = protocol.read_response(NotImplemented, stream_id=42) + + assert resp.stream_id == 42 + assert resp.http_version == "HTTP/2.0" + assert resp.status_code == 200 + assert resp.msg == '' + assert resp.headers.fields == [[b':status', b'200'], [b'etag', b'foobar']] + assert resp.content == b'' + + +class TestAssembleRequest(object): + c = tcp.TCPClient(("127.0.0.1", 0)) + + def test_request_simple(self): + bytes = HTTP2Protocol(self.c).assemble_request(http.Request( + b'', + b'GET', + b'https', + b'', + b'', + b'/', + b"HTTP/2.0", + None, + None, + )) + assert len(bytes) == 1 + assert bytes[0] == codecs.decode('00000d0105000000018284874188089d5c0b8170dc07', 'hex_codec') + + def test_request_with_stream_id(self): + req = http.Request( + b'', + b'GET', + b'https', + b'', + b'', + b'/', + b"HTTP/2.0", + None, + None, + ) + req.stream_id = 0x42 + bytes = HTTP2Protocol(self.c).assemble_request(req) + assert len(bytes) == 1 + assert bytes[0] == codecs.decode('00000d0105000000428284874188089d5c0b8170dc07', 'hex_codec') + + def test_request_with_body(self): + bytes = HTTP2Protocol(self.c).assemble_request(http.Request( + b'', + b'GET', + b'https', + b'', + b'', + b'/', + b"HTTP/2.0", + http.Headers([(b'foo', b'bar')]), + b'foobar', + )) + assert len(bytes) == 2 + assert bytes[0] ==\ + codecs.decode('0000150104000000018284874188089d5c0b8170dc07408294e7838c767f', 'hex_codec') + assert bytes[1] ==\ + codecs.decode('000006000100000001666f6f626172', 'hex_codec') + + +class TestAssembleResponse(object): + c = tcp.TCPClient(("127.0.0.1", 0)) + + def test_simple(self): + bytes = HTTP2Protocol(self.c, is_server=True).assemble_response(http.Response( + b"HTTP/2.0", + 200, + )) + assert len(bytes) == 1 + assert bytes[0] ==\ + codecs.decode('00000101050000000288', 'hex_codec') + + def test_with_stream_id(self): + resp = http.Response( + b"HTTP/2.0", + 200, + ) + resp.stream_id = 0x42 + bytes = HTTP2Protocol(self.c, is_server=True).assemble_response(resp) + assert len(bytes) == 1 + assert bytes[0] ==\ + codecs.decode('00000101050000004288', 'hex_codec') + + def test_with_body(self): + bytes = HTTP2Protocol(self.c, is_server=True).assemble_response(http.Response( + b"HTTP/2.0", + 200, + b'', + http.Headers(foo=b"bar"), + b'foobar' + )) + assert len(bytes) == 2 + assert bytes[0] ==\ + codecs.decode('00000901040000000288408294e7838c767f', 'hex_codec') + assert bytes[1] ==\ + codecs.decode('000006000100000002666f6f626172', 'hex_codec') diff --git a/test/netlib/http/test_authentication.py b/test/netlib/http/test_authentication.py new file mode 100644 index 00000000..1df7cd9c --- /dev/null +++ b/test/netlib/http/test_authentication.py @@ -0,0 +1,122 @@ +import binascii + +from netlib import tutils +from netlib.http import authentication, Headers + + +def test_parse_http_basic_auth(): + vals = ("basic", "foo", "bar") + assert authentication.parse_http_basic_auth( + authentication.assemble_http_basic_auth(*vals) + ) == vals + assert not authentication.parse_http_basic_auth("") + assert not authentication.parse_http_basic_auth("foo bar") + v = "basic " + binascii.b2a_base64(b"foo").decode("ascii") + assert not authentication.parse_http_basic_auth(v) + + +class TestPassManNonAnon: + + def test_simple(self): + p = authentication.PassManNonAnon() + assert not p.test("", "") + assert p.test("user", "") + + +class TestPassManHtpasswd: + + def test_file_errors(self): + tutils.raises( + "malformed htpasswd file", + authentication.PassManHtpasswd, + tutils.test_data.path("data/server.crt")) + + def test_simple(self): + pm = authentication.PassManHtpasswd(tutils.test_data.path("data/htpasswd")) + + vals = ("basic", "test", "test") + authentication.assemble_http_basic_auth(*vals) + assert pm.test("test", "test") + assert not pm.test("test", "foo") + assert not pm.test("foo", "test") + assert not pm.test("test", "") + assert not pm.test("", "") + + +class TestPassManSingleUser: + + def test_simple(self): + pm = authentication.PassManSingleUser("test", "test") + assert pm.test("test", "test") + assert not pm.test("test", "foo") + assert not pm.test("foo", "test") + + +class TestNullProxyAuth: + + def test_simple(self): + na = authentication.NullProxyAuth(authentication.PassManNonAnon()) + assert not na.auth_challenge_headers() + assert na.authenticate("foo") + na.clean({}) + + +class TestBasicProxyAuth: + + def test_simple(self): + ba = authentication.BasicProxyAuth(authentication.PassManNonAnon(), "test") + headers = Headers() + assert ba.auth_challenge_headers() + assert not ba.authenticate(headers) + + def test_authenticate_clean(self): + ba = authentication.BasicProxyAuth(authentication.PassManNonAnon(), "test") + + headers = Headers() + vals = ("basic", "foo", "bar") + headers[ba.AUTH_HEADER] = authentication.assemble_http_basic_auth(*vals) + assert ba.authenticate(headers) + + ba.clean(headers) + assert not ba.AUTH_HEADER in headers + + headers[ba.AUTH_HEADER] = "" + assert not ba.authenticate(headers) + + headers[ba.AUTH_HEADER] = "foo" + assert not ba.authenticate(headers) + + vals = ("foo", "foo", "bar") + headers[ba.AUTH_HEADER] = authentication.assemble_http_basic_auth(*vals) + assert not ba.authenticate(headers) + + ba = authentication.BasicProxyAuth(authentication.PassMan(), "test") + vals = ("basic", "foo", "bar") + headers[ba.AUTH_HEADER] = authentication.assemble_http_basic_auth(*vals) + assert not ba.authenticate(headers) + + +class Bunch: + pass + + +class TestAuthAction: + + def test_nonanonymous(self): + m = Bunch() + aa = authentication.NonanonymousAuthAction(None, "authenticator") + aa(None, m, None, None) + assert m.authenticator + + def test_singleuser(self): + m = Bunch() + aa = authentication.SingleuserAuthAction(None, "authenticator") + aa(None, m, "foo:bar", None) + assert m.authenticator + tutils.raises("invalid", aa, None, m, "foo", None) + + def test_httppasswd(self): + m = Bunch() + aa = authentication.HtpasswdAuthAction(None, "authenticator") + aa(None, m, tutils.test_data.path("data/htpasswd"), None) + assert m.authenticator diff --git a/test/netlib/http/test_cookies.py b/test/netlib/http/test_cookies.py new file mode 100644 index 00000000..34bb64f2 --- /dev/null +++ b/test/netlib/http/test_cookies.py @@ -0,0 +1,218 @@ +from netlib.http import cookies + + +def test_read_token(): + tokens = [ + [("foo", 0), ("foo", 3)], + [("foo", 1), ("oo", 3)], + [(" foo", 1), ("foo", 4)], + [(" foo;", 1), ("foo", 4)], + [(" foo=", 1), ("foo", 4)], + [(" foo=bar", 1), ("foo", 4)], + ] + for q, a in tokens: + assert cookies._read_token(*q) == a + + +def test_read_quoted_string(): + tokens = [ + [('"foo" x', 0), ("foo", 5)], + [('"f\oo" x', 0), ("foo", 6)], + [(r'"f\\o" x', 0), (r"f\o", 6)], + [(r'"f\\" x', 0), (r"f" + '\\', 5)], + [('"fo\\\"" x', 0), ("fo\"", 6)], + [('"foo" x', 7), ("", 8)], + ] + for q, a in tokens: + assert cookies._read_quoted_string(*q) == a + + +def test_read_pairs(): + vals = [ + [ + "one", + [["one", None]] + ], + [ + "one=two", + [["one", "two"]] + ], + [ + "one=", + [["one", ""]] + ], + [ + 'one="two"', + [["one", "two"]] + ], + [ + 'one="two"; three=four', + [["one", "two"], ["three", "four"]] + ], + [ + 'one="two"; three=four; five', + [["one", "two"], ["three", "four"], ["five", None]] + ], + [ + 'one="\\"two"; three=four', + [["one", '"two'], ["three", "four"]] + ], + ] + for s, lst in vals: + ret, off = cookies._read_pairs(s) + assert ret == lst + + +def test_pairs_roundtrips(): + pairs = [ + [ + "", + [] + ], + [ + "one=uno", + [["one", "uno"]] + ], + [ + "one", + [["one", None]] + ], + [ + "one=uno; two=due", + [["one", "uno"], ["two", "due"]] + ], + [ + 'one="uno"; two="\due"', + [["one", "uno"], ["two", "due"]] + ], + [ + 'one="un\\"o"', + [["one", 'un"o']] + ], + [ + 'one="uno,due"', + [["one", 'uno,due']] + ], + [ + "one=uno; two; three=tre", + [["one", "uno"], ["two", None], ["three", "tre"]] + ], + [ + "_lvs2=zHai1+Hq+Tc2vmc2r4GAbdOI5Jopg3EwsdUT9g=; " + "_rcc2=53VdltWl+Ov6ordflA==;", + [ + ["_lvs2", "zHai1+Hq+Tc2vmc2r4GAbdOI5Jopg3EwsdUT9g="], + ["_rcc2", "53VdltWl+Ov6ordflA=="] + ] + ] + ] + for s, lst in pairs: + ret, off = cookies._read_pairs(s) + assert ret == lst + s2 = cookies._format_pairs(lst) + ret, off = cookies._read_pairs(s2) + assert ret == lst + + +def test_cookie_roundtrips(): + pairs = [ + [ + "one=uno", + [["one", "uno"]] + ], + [ + "one=uno; two=due", + [["one", "uno"], ["two", "due"]] + ], + ] + for s, lst in pairs: + ret = cookies.parse_cookie_header(s) + assert ret.lst == lst + s2 = cookies.format_cookie_header(ret) + ret = cookies.parse_cookie_header(s2) + assert ret.lst == lst + + +def test_parse_set_cookie_pairs(): + pairs = [ + [ + "one=uno", + [ + ["one", "uno"] + ] + ], + [ + "one=un\x20", + [ + ["one", "un\x20"] + ] + ], + [ + "one=uno; foo", + [ + ["one", "uno"], + ["foo", None] + ] + ], + [ + "mun=1.390.f60; " + "expires=sun, 11-oct-2015 12:38:31 gmt; path=/; " + "domain=b.aol.com", + [ + ["mun", "1.390.f60"], + ["expires", "sun, 11-oct-2015 12:38:31 gmt"], + ["path", "/"], + ["domain", "b.aol.com"] + ] + ], + [ + r'rpb=190%3d1%2616726%3d1%2634832%3d1%2634874%3d1; ' + 'domain=.rubiconproject.com; ' + 'expires=mon, 11-may-2015 21:54:57 gmt; ' + 'path=/', + [ + ['rpb', r'190%3d1%2616726%3d1%2634832%3d1%2634874%3d1'], + ['domain', '.rubiconproject.com'], + ['expires', 'mon, 11-may-2015 21:54:57 gmt'], + ['path', '/'] + ] + ], + ] + for s, lst in pairs: + ret = cookies._parse_set_cookie_pairs(s) + assert ret == lst + s2 = cookies._format_set_cookie_pairs(ret) + ret2 = cookies._parse_set_cookie_pairs(s2) + assert ret2 == lst + + +def test_parse_set_cookie_header(): + vals = [ + [ + "", None + ], + [ + ";", None + ], + [ + "one=uno", + ("one", "uno", []) + ], + [ + "one=uno; foo=bar", + ("one", "uno", [["foo", "bar"]]) + ] + ] + for s, expected in vals: + ret = cookies.parse_set_cookie_header(s) + if expected: + assert ret[0] == expected[0] + assert ret[1] == expected[1] + assert ret[2].lst == expected[2] + s2 = cookies.format_set_cookie_header(*ret) + ret2 = cookies.parse_set_cookie_header(s2) + assert ret2[0] == expected[0] + assert ret2[1] == expected[1] + assert ret2[2].lst == expected[2] + else: + assert ret is None diff --git a/test/netlib/http/test_headers.py b/test/netlib/http/test_headers.py new file mode 100644 index 00000000..d50fee3e --- /dev/null +++ b/test/netlib/http/test_headers.py @@ -0,0 +1,152 @@ +from netlib.http import Headers +from netlib.tutils import raises + + +class TestHeaders(object): + def _2host(self): + return Headers( + [ + [b"Host", b"example.com"], + [b"host", b"example.org"] + ] + ) + + def test_init(self): + headers = Headers() + assert len(headers) == 0 + + headers = Headers([[b"Host", b"example.com"]]) + assert len(headers) == 1 + assert headers["Host"] == "example.com" + + headers = Headers(Host="example.com") + assert len(headers) == 1 + assert headers["Host"] == "example.com" + + headers = Headers( + [[b"Host", b"invalid"]], + Host="example.com" + ) + assert len(headers) == 1 + assert headers["Host"] == "example.com" + + headers = Headers( + [[b"Host", b"invalid"], [b"Accept", b"text/plain"]], + Host="example.com" + ) + assert len(headers) == 2 + assert headers["Host"] == "example.com" + assert headers["Accept"] == "text/plain" + + with raises(ValueError): + Headers([[b"Host", u"not-bytes"]]) + + def test_getitem(self): + headers = Headers(Host="example.com") + assert headers["Host"] == "example.com" + assert headers["host"] == "example.com" + with raises(KeyError): + _ = headers["Accept"] + + headers = self._2host() + assert headers["Host"] == "example.com, example.org" + + def test_str(self): + headers = Headers(Host="example.com") + assert bytes(headers) == b"Host: example.com\r\n" + + headers = Headers([ + [b"Host", b"example.com"], + [b"Accept", b"text/plain"] + ]) + assert bytes(headers) == b"Host: example.com\r\nAccept: text/plain\r\n" + + headers = Headers() + assert bytes(headers) == b"" + + def test_setitem(self): + headers = Headers() + headers["Host"] = "example.com" + assert "Host" in headers + assert "host" in headers + assert headers["Host"] == "example.com" + + headers["host"] = "example.org" + assert "Host" in headers + assert "host" in headers + assert headers["Host"] == "example.org" + + headers["accept"] = "text/plain" + assert len(headers) == 2 + assert "Accept" in headers + assert "Host" in headers + + headers = self._2host() + assert len(headers.fields) == 2 + headers["Host"] = "example.com" + assert len(headers.fields) == 1 + assert "Host" in headers + + def test_delitem(self): + headers = Headers(Host="example.com") + assert len(headers) == 1 + del headers["host"] + assert len(headers) == 0 + try: + del headers["host"] + except KeyError: + assert True + else: + assert False + + headers = self._2host() + del headers["Host"] + assert len(headers) == 0 + + def test_keys(self): + headers = Headers(Host="example.com") + assert list(headers.keys()) == ["Host"] + + headers = self._2host() + assert list(headers.keys()) == ["Host"] + + def test_eq_ne(self): + headers1 = Headers(Host="example.com") + headers2 = Headers(host="example.com") + assert not (headers1 == headers2) + assert headers1 != headers2 + + headers1 = Headers(Host="example.com") + headers2 = Headers(Host="example.com") + assert headers1 == headers2 + assert not (headers1 != headers2) + + assert headers1 != 42 + + def test_get_all(self): + headers = self._2host() + assert headers.get_all("host") == ["example.com", "example.org"] + assert headers.get_all("accept") == [] + + def test_set_all(self): + headers = Headers(Host="example.com") + headers.set_all("Accept", ["text/plain"]) + assert len(headers) == 2 + assert "accept" in headers + + headers = self._2host() + headers.set_all("Host", ["example.org"]) + assert headers["host"] == "example.org" + + headers.set_all("Host", ["example.org", "example.net"]) + assert headers["host"] == "example.org, example.net" + + def test_state(self): + headers = self._2host() + assert len(headers.get_state()) == 2 + assert headers == Headers.from_state(headers.get_state()) + + headers2 = Headers() + assert headers != headers2 + headers2.set_state(headers.get_state()) + assert headers == headers2 diff --git a/test/netlib/http/test_message.py b/test/netlib/http/test_message.py new file mode 100644 index 00000000..4b1f4630 --- /dev/null +++ b/test/netlib/http/test_message.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, division + +from netlib.http import decoded, Headers +from netlib.tutils import tresp, raises + + +def _test_passthrough_attr(message, attr): + assert getattr(message, attr) == getattr(message.data, attr) + setattr(message, attr, "foo") + assert getattr(message.data, attr) == "foo" + + +def _test_decoded_attr(message, attr): + assert getattr(message, attr) == getattr(message.data, attr).decode("utf8") + # Set str, get raw bytes + setattr(message, attr, "foo") + assert getattr(message.data, attr) == b"foo" + # Set raw bytes, get decoded + setattr(message.data, attr, b"BAR") # use uppercase so that we can also cover request.method + assert getattr(message, attr) == "BAR" + # Set bytes, get raw bytes + setattr(message, attr, b"baz") + assert getattr(message.data, attr) == b"baz" + + # Set UTF8 + setattr(message, attr, "Non-Autorisé") + assert getattr(message.data, attr) == b"Non-Autoris\xc3\xa9" + # Don't fail on garbage + setattr(message.data, attr, b"FOO\xFF\x00BAR") + assert getattr(message, attr).startswith("FOO") + assert getattr(message, attr).endswith("BAR") + # foo.bar = foo.bar should not cause any side effects. + d = getattr(message, attr) + setattr(message, attr, d) + assert getattr(message.data, attr) == b"FOO\xFF\x00BAR" + + +class TestMessageData(object): + def test_eq_ne(self): + data = tresp(timestamp_start=42, timestamp_end=42).data + same = tresp(timestamp_start=42, timestamp_end=42).data + assert data == same + assert not data != same + + other = tresp(content=b"foo").data + assert not data == other + assert data != other + + assert data != 0 + + +class TestMessage(object): + + def test_init(self): + resp = tresp() + assert resp.data + + def test_eq_ne(self): + resp = tresp(timestamp_start=42, timestamp_end=42) + same = tresp(timestamp_start=42, timestamp_end=42) + assert resp == same + assert not resp != same + + other = tresp(timestamp_start=0, timestamp_end=0) + assert not resp == other + assert resp != other + + assert resp != 0 + + def test_content_length_update(self): + resp = tresp() + resp.content = b"foo" + assert resp.data.content == b"foo" + assert resp.headers["content-length"] == "3" + resp.content = b"" + assert resp.data.content == b"" + assert resp.headers["content-length"] == "0" + + def test_content_basic(self): + _test_passthrough_attr(tresp(), "content") + + def test_headers(self): + _test_passthrough_attr(tresp(), "headers") + + def test_timestamp_start(self): + _test_passthrough_attr(tresp(), "timestamp_start") + + def test_timestamp_end(self): + _test_passthrough_attr(tresp(), "timestamp_end") + + def teste_http_version(self): + _test_decoded_attr(tresp(), "http_version") + + +class TestDecodedDecorator(object): + + def test_simple(self): + r = tresp() + assert r.content == b"message" + assert "content-encoding" not in r.headers + assert r.encode("gzip") + + assert r.headers["content-encoding"] + assert r.content != b"message" + with decoded(r): + assert "content-encoding" not in r.headers + assert r.content == b"message" + assert r.headers["content-encoding"] + assert r.content != b"message" + + def test_modify(self): + r = tresp() + assert "content-encoding" not in r.headers + assert r.encode("gzip") + + with decoded(r): + r.content = b"foo" + + assert r.content != b"foo" + r.decode() + assert r.content == b"foo" + + def test_unknown_ce(self): + r = tresp() + r.headers["content-encoding"] = "zopfli" + r.content = b"foo" + with decoded(r): + assert r.headers["content-encoding"] + assert r.content == b"foo" + assert r.headers["content-encoding"] + assert r.content == b"foo" + + def test_cannot_decode(self): + r = tresp() + assert r.encode("gzip") + r.content = b"foo" + with decoded(r): + assert r.headers["content-encoding"] + assert r.content == b"foo" + assert r.headers["content-encoding"] + assert r.content != b"foo" + r.decode() + assert r.content == b"foo" + + def test_cannot_encode(self): + r = tresp() + assert r.encode("gzip") + with decoded(r): + r.content = None + + assert "content-encoding" not in r.headers + assert r.content is None diff --git a/test/netlib/http/test_request.py b/test/netlib/http/test_request.py new file mode 100644 index 00000000..900b2cd1 --- /dev/null +++ b/test/netlib/http/test_request.py @@ -0,0 +1,238 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, division + +import six + +from netlib import utils +from netlib.http import Headers +from netlib.odict import ODict +from netlib.tutils import treq, raises +from .test_message import _test_decoded_attr, _test_passthrough_attr + + +class TestRequestData(object): + def test_init(self): + with raises(ValueError if six.PY2 else TypeError): + treq(headers="foobar") + + assert isinstance(treq(headers=None).headers, Headers) + + +class TestRequestCore(object): + """ + Tests for builtins and the attributes that are directly proxied from the data structure + """ + def test_repr(self): + request = treq() + assert repr(request) == "Request(GET address:22/path)" + request.host = None + assert repr(request) == "Request(GET /path)" + + def test_first_line_format(self): + _test_passthrough_attr(treq(), "first_line_format") + + def test_method(self): + _test_decoded_attr(treq(), "method") + + def test_scheme(self): + _test_decoded_attr(treq(), "scheme") + + def test_port(self): + _test_passthrough_attr(treq(), "port") + + def test_path(self): + _test_decoded_attr(treq(), "path") + + def test_host(self): + if six.PY2: + from unittest import SkipTest + raise SkipTest() + + request = treq() + assert request.host == request.data.host.decode("idna") + + # Test IDNA encoding + # Set str, get raw bytes + request.host = "Ãdna.example" + assert request.data.host == b"xn--dna-qma.example" + # Set raw bytes, get decoded + request.data.host = b"xn--idn-gla.example" + assert request.host == "idná.example" + # Set bytes, get raw bytes + request.host = b"xn--dn-qia9b.example" + assert request.data.host == b"xn--dn-qia9b.example" + # IDNA encoding is not bijective + request.host = "fußball" + assert request.host == "fussball" + + # Don't fail on garbage + request.data.host = b"foo\xFF\x00bar" + assert request.host.startswith("foo") + assert request.host.endswith("bar") + # foo.bar = foo.bar should not cause any side effects. + d = request.host + request.host = d + assert request.data.host == b"foo\xFF\x00bar" + + def test_host_header_update(self): + request = treq() + assert "host" not in request.headers + request.host = "example.com" + assert "host" not in request.headers + + request.headers["Host"] = "foo" + request.host = "example.org" + assert request.headers["Host"] == "example.org" + + +class TestRequestUtils(object): + """ + Tests for additional convenience methods. + """ + def test_url(self): + request = treq() + assert request.url == "http://address:22/path" + + request.url = "https://otheraddress:42/foo" + assert request.scheme == "https" + assert request.host == "otheraddress" + assert request.port == 42 + assert request.path == "/foo" + + with raises(ValueError): + request.url = "not-a-url" + + def test_pretty_host(self): + request = treq() + assert request.pretty_host == "address" + assert request.host == "address" + request.headers["host"] = "other" + assert request.pretty_host == "other" + assert request.host == "address" + request.host = None + assert request.pretty_host is None + assert request.host is None + + # Invalid IDNA + request.headers["host"] = ".disqus.com" + assert request.pretty_host == ".disqus.com" + + def test_pretty_url(self): + request = treq() + assert request.url == "http://address:22/path" + assert request.pretty_url == "http://address:22/path" + request.headers["host"] = "other" + assert request.pretty_url == "http://other:22/path" + + def test_pretty_url_authority(self): + request = treq(first_line_format="authority") + assert request.pretty_url == "address:22" + + def test_get_query(self): + request = treq() + assert request.query is None + + request.url = "http://localhost:80/foo?bar=42" + assert request.query.lst == [("bar", "42")] + + def test_set_query(self): + request = treq() + request.query = ODict([]) + + def test_get_cookies_none(self): + request = treq() + request.headers = Headers() + assert len(request.cookies) == 0 + + def test_get_cookies_single(self): + request = treq() + request.headers = Headers(cookie="cookiename=cookievalue") + result = request.cookies + assert len(result) == 1 + assert result['cookiename'] == ['cookievalue'] + + def test_get_cookies_double(self): + request = treq() + request.headers = Headers(cookie="cookiename=cookievalue;othercookiename=othercookievalue") + result = request.cookies + assert len(result) == 2 + assert result['cookiename'] == ['cookievalue'] + assert result['othercookiename'] == ['othercookievalue'] + + def test_get_cookies_withequalsign(self): + request = treq() + request.headers = Headers(cookie="cookiename=coo=kievalue;othercookiename=othercookievalue") + result = request.cookies + assert len(result) == 2 + assert result['cookiename'] == ['coo=kievalue'] + assert result['othercookiename'] == ['othercookievalue'] + + def test_set_cookies(self): + request = treq() + request.headers = Headers(cookie="cookiename=cookievalue") + result = request.cookies + result["cookiename"] = ["foo"] + request.cookies = result + assert request.cookies["cookiename"] == ["foo"] + + def test_get_path_components(self): + request = treq(path=b"/foo/bar") + assert request.path_components == ["foo", "bar"] + + def test_set_path_components(self): + request = treq() + request.path_components = ["foo", "baz"] + assert request.path == "/foo/baz" + request.path_components = [] + assert request.path == "/" + + def test_anticache(self): + request = treq() + request.headers["If-Modified-Since"] = "foo" + request.headers["If-None-Match"] = "bar" + request.anticache() + assert "If-Modified-Since" not in request.headers + assert "If-None-Match" not in request.headers + + def test_anticomp(self): + request = treq() + request.headers["Accept-Encoding"] = "foobar" + request.anticomp() + assert request.headers["Accept-Encoding"] == "identity" + + def test_constrain_encoding(self): + request = treq() + + h = request.headers.copy() + request.constrain_encoding() # no-op if there is no accept_encoding header. + assert request.headers == h + + request.headers["Accept-Encoding"] = "identity, gzip, foo" + request.constrain_encoding() + assert "foo" not in request.headers["Accept-Encoding"] + assert "gzip" in request.headers["Accept-Encoding"] + + def test_get_urlencoded_form(self): + request = treq(content="foobar") + assert request.urlencoded_form is None + + request.headers["Content-Type"] = "application/x-www-form-urlencoded" + assert request.urlencoded_form == ODict(utils.urldecode(request.content)) + + def test_set_urlencoded_form(self): + request = treq() + request.urlencoded_form = ODict([('foo', 'bar'), ('rab', 'oof')]) + assert request.headers["Content-Type"] == "application/x-www-form-urlencoded" + assert request.content + + def test_get_multipart_form(self): + request = treq(content="foobar") + assert request.multipart_form is None + + request.headers["Content-Type"] = "multipart/form-data" + assert request.multipart_form == ODict( + utils.multipartdecode( + request.headers, + request.content + ) + ) diff --git a/test/netlib/http/test_response.py b/test/netlib/http/test_response.py new file mode 100644 index 00000000..14588000 --- /dev/null +++ b/test/netlib/http/test_response.py @@ -0,0 +1,102 @@ +from __future__ import absolute_import, print_function, division + +import six + +from netlib.http import Headers +from netlib.odict import ODict, ODictCaseless +from netlib.tutils import raises, tresp +from .test_message import _test_passthrough_attr, _test_decoded_attr + + +class TestResponseData(object): + def test_init(self): + with raises(ValueError if six.PY2 else TypeError): + tresp(headers="foobar") + + assert isinstance(tresp(headers=None).headers, Headers) + + +class TestResponseCore(object): + """ + Tests for builtins and the attributes that are directly proxied from the data structure + """ + def test_repr(self): + response = tresp() + assert repr(response) == "Response(200 OK, unknown content type, 7B)" + response.content = None + assert repr(response) == "Response(200 OK, no content)" + + def test_status_code(self): + _test_passthrough_attr(tresp(), "status_code") + + def test_reason(self): + _test_decoded_attr(tresp(), "reason") + + +class TestResponseUtils(object): + """ + Tests for additional convenience methods. + """ + def test_get_cookies_none(self): + resp = tresp() + resp.headers = Headers() + assert not resp.cookies + + def test_get_cookies_empty(self): + resp = tresp() + resp.headers = Headers(set_cookie="") + assert not resp.cookies + + def test_get_cookies_simple(self): + resp = tresp() + resp.headers = Headers(set_cookie="cookiename=cookievalue") + result = resp.cookies + assert len(result) == 1 + assert "cookiename" in result + assert result["cookiename"][0] == ["cookievalue", ODict()] + + def test_get_cookies_with_parameters(self): + resp = tresp() + resp.headers = Headers(set_cookie="cookiename=cookievalue;domain=example.com;expires=Wed Oct 21 16:29:41 2015;path=/; HttpOnly") + result = resp.cookies + assert len(result) == 1 + assert "cookiename" in result + assert result["cookiename"][0][0] == "cookievalue" + attrs = result["cookiename"][0][1] + assert len(attrs) == 4 + assert attrs["domain"] == ["example.com"] + assert attrs["expires"] == ["Wed Oct 21 16:29:41 2015"] + assert attrs["path"] == ["/"] + assert attrs["httponly"] == [None] + + def test_get_cookies_no_value(self): + resp = tresp() + resp.headers = Headers(set_cookie="cookiename=; Expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/") + result = resp.cookies + assert len(result) == 1 + assert "cookiename" in result + assert result["cookiename"][0][0] == "" + assert len(result["cookiename"][0][1]) == 2 + + def test_get_cookies_twocookies(self): + resp = tresp() + resp.headers = Headers([ + [b"Set-Cookie", b"cookiename=cookievalue"], + [b"Set-Cookie", b"othercookie=othervalue"] + ]) + result = resp.cookies + assert len(result) == 2 + assert "cookiename" in result + assert result["cookiename"][0] == ["cookievalue", ODict()] + assert "othercookie" in result + assert result["othercookie"][0] == ["othervalue", ODict()] + + def test_set_cookies(self): + resp = tresp() + v = resp.cookies + v.add("foo", ["bar", ODictCaseless()]) + resp.set_cookies(v) + + v = resp.cookies + assert len(v) == 1 + assert v["foo"] == [["bar", ODictCaseless()]] diff --git a/test/netlib/http/test_status_codes.py b/test/netlib/http/test_status_codes.py new file mode 100644 index 00000000..9fea6b70 --- /dev/null +++ b/test/netlib/http/test_status_codes.py @@ -0,0 +1,6 @@ +from netlib.http import status_codes + + +def test_simple(): + assert status_codes.IM_A_TEAPOT == 418 + assert status_codes.RESPONSES[418] == "I'm a teapot" diff --git a/test/netlib/http/test_user_agents.py b/test/netlib/http/test_user_agents.py new file mode 100644 index 00000000..0bf1bba7 --- /dev/null +++ b/test/netlib/http/test_user_agents.py @@ -0,0 +1,6 @@ +from netlib.http import user_agents + + +def test_get_shortcut(): + assert user_agents.get_by_shortcut("c")[0] == "chrome" + assert not user_agents.get_by_shortcut("_") diff --git a/test/netlib/test_certutils.py b/test/netlib/test_certutils.py new file mode 100644 index 00000000..027dcc93 --- /dev/null +++ b/test/netlib/test_certutils.py @@ -0,0 +1,155 @@ +import os +from netlib import certutils, tutils + +# class TestDNTree: +# def test_simple(self): +# d = certutils.DNTree() +# d.add("foo.com", "foo") +# d.add("bar.com", "bar") +# assert d.get("foo.com") == "foo" +# assert d.get("bar.com") == "bar" +# assert not d.get("oink.com") +# assert not d.get("oink") +# assert not d.get("") +# assert not d.get("oink.oink") +# +# d.add("*.match.org", "match") +# assert not d.get("match.org") +# assert d.get("foo.match.org") == "match" +# assert d.get("foo.foo.match.org") == "match" +# +# def test_wildcard(self): +# d = certutils.DNTree() +# d.add("foo.com", "foo") +# assert not d.get("*.foo.com") +# d.add("*.foo.com", "wild") +# +# d = certutils.DNTree() +# d.add("*", "foo") +# assert d.get("foo.com") == "foo" +# assert d.get("*.foo.com") == "foo" +# assert d.get("com") == "foo" + + +class TestCertStore: + + def test_create_explicit(self): + with tutils.tmpdir() as d: + ca = certutils.CertStore.from_store(d, "test") + assert ca.get_cert(b"foo", []) + + ca2 = certutils.CertStore.from_store(d, "test") + assert ca2.get_cert(b"foo", []) + + assert ca.default_ca.get_serial_number() == ca2.default_ca.get_serial_number() + + def test_create_no_common_name(self): + with tutils.tmpdir() as d: + ca = certutils.CertStore.from_store(d, "test") + assert ca.get_cert(None, [])[0].cn is None + + def test_create_tmp(self): + with tutils.tmpdir() as d: + ca = certutils.CertStore.from_store(d, "test") + assert ca.get_cert(b"foo.com", []) + assert ca.get_cert(b"foo.com", []) + assert ca.get_cert(b"*.foo.com", []) + + r = ca.get_cert(b"*.foo.com", []) + assert r[1] == ca.default_privatekey + + def test_sans(self): + with tutils.tmpdir() as d: + ca = certutils.CertStore.from_store(d, "test") + c1 = ca.get_cert(b"foo.com", [b"*.bar.com"]) + ca.get_cert(b"foo.bar.com", []) + # assert c1 == c2 + c3 = ca.get_cert(b"bar.com", []) + assert not c1 == c3 + + def test_sans_change(self): + with tutils.tmpdir() as d: + ca = certutils.CertStore.from_store(d, "test") + ca.get_cert(b"foo.com", [b"*.bar.com"]) + cert, key, chain_file = ca.get_cert(b"foo.bar.com", [b"*.baz.com"]) + assert b"*.baz.com" in cert.altnames + + def test_overrides(self): + with tutils.tmpdir() as d: + ca1 = certutils.CertStore.from_store(os.path.join(d, "ca1"), "test") + ca2 = certutils.CertStore.from_store(os.path.join(d, "ca2"), "test") + assert not ca1.default_ca.get_serial_number( + ) == ca2.default_ca.get_serial_number() + + dc = ca2.get_cert(b"foo.com", [b"sans.example.com"]) + dcp = os.path.join(d, "dc") + f = open(dcp, "wb") + f.write(dc[0].to_pem()) + f.close() + ca1.add_cert_file(b"foo.com", dcp) + + ret = ca1.get_cert(b"foo.com", []) + assert ret[0].serial == dc[0].serial + + +class TestDummyCert: + + def test_with_ca(self): + with tutils.tmpdir() as d: + ca = certutils.CertStore.from_store(d, "test") + r = certutils.dummy_cert( + ca.default_privatekey, + ca.default_ca, + b"foo.com", + [b"one.com", b"two.com", b"*.three.com"] + ) + assert r.cn == b"foo.com" + + r = certutils.dummy_cert( + ca.default_privatekey, + ca.default_ca, + None, + [] + ) + assert r.cn is None + + +class TestSSLCert: + + def test_simple(self): + with open(tutils.test_data.path("data/text_cert"), "rb") as f: + d = f.read() + c1 = certutils.SSLCert.from_pem(d) + assert c1.cn == b"google.com" + assert len(c1.altnames) == 436 + + with open(tutils.test_data.path("data/text_cert_2"), "rb") as f: + d = f.read() + c2 = certutils.SSLCert.from_pem(d) + assert c2.cn == b"www.inode.co.nz" + assert len(c2.altnames) == 2 + assert c2.digest("sha1") + assert c2.notbefore + assert c2.notafter + assert c2.subject + assert c2.keyinfo == ("RSA", 2048) + assert c2.serial + assert c2.issuer + assert c2.to_pem() + assert c2.has_expired is not None + + assert not c1 == c2 + assert c1 != c2 + + def test_err_broken_sans(self): + with open(tutils.test_data.path("data/text_cert_weird1"), "rb") as f: + d = f.read() + c = certutils.SSLCert.from_pem(d) + # This breaks unless we ignore a decoding error. + assert c.altnames is not None + + def test_der(self): + with open(tutils.test_data.path("data/dercert"), "rb") as f: + d = f.read() + s = certutils.SSLCert.from_der(d) + assert s.cn diff --git a/test/netlib/test_encoding.py b/test/netlib/test_encoding.py new file mode 100644 index 00000000..0ff1aad1 --- /dev/null +++ b/test/netlib/test_encoding.py @@ -0,0 +1,37 @@ +from netlib import encoding + + +def test_identity(): + assert b"string" == encoding.decode("identity", b"string") + assert b"string" == encoding.encode("identity", b"string") + assert not encoding.encode("nonexistent", b"string") + assert not encoding.decode("nonexistent encoding", b"string") + + +def test_gzip(): + assert b"string" == encoding.decode( + "gzip", + encoding.encode( + "gzip", + b"string" + ) + ) + assert encoding.decode("gzip", b"bogus") is None + + +def test_deflate(): + assert b"string" == encoding.decode( + "deflate", + encoding.encode( + "deflate", + b"string" + ) + ) + assert b"string" == encoding.decode( + "deflate", + encoding.encode( + "deflate", + b"string" + )[2:-4] + ) + assert encoding.decode("deflate", b"bogus") is None diff --git a/test/netlib/test_imports.py b/test/netlib/test_imports.py new file mode 100644 index 00000000..b88ef26d --- /dev/null +++ b/test/netlib/test_imports.py @@ -0,0 +1 @@ +# These are actually tests! diff --git a/test/netlib/test_odict.py b/test/netlib/test_odict.py new file mode 100644 index 00000000..f0985ef6 --- /dev/null +++ b/test/netlib/test_odict.py @@ -0,0 +1,153 @@ +from netlib import odict, tutils + + +class TestODict(object): + + def test_repr(self): + h = odict.ODict() + h["one"] = ["two"] + assert repr(h) + + def test_str_err(self): + h = odict.ODict() + with tutils.raises(ValueError): + h["key"] = u"foo" + with tutils.raises(ValueError): + h["key"] = b"foo" + + def test_getset_state(self): + od = odict.ODict() + od.add("foo", 1) + od.add("foo", 2) + od.add("bar", 3) + state = od.get_state() + nd = odict.ODict.from_state(state) + assert nd == od + b = odict.ODict() + b.set_state(state) + assert b == od + + def test_in_any(self): + od = odict.ODict() + od["one"] = ["atwoa", "athreea"] + assert od.in_any("one", "two") + assert od.in_any("one", "three") + assert not od.in_any("one", "four") + assert not od.in_any("nonexistent", "foo") + assert not od.in_any("one", "TWO") + assert od.in_any("one", "TWO", True) + + def test_iter(self): + od = odict.ODict() + assert not [i for i in od] + od.add("foo", 1) + assert [i for i in od] + + def test_keys(self): + od = odict.ODict() + assert not od.keys() + od.add("foo", 1) + assert od.keys() == ["foo"] + od.add("foo", 2) + assert od.keys() == ["foo"] + od.add("bar", 2) + assert len(od.keys()) == 2 + + def test_copy(self): + od = odict.ODict() + od.add("foo", 1) + od.add("foo", 2) + od.add("bar", 3) + assert od == od.copy() + assert not od != od.copy() + + def test_del(self): + od = odict.ODict() + od.add("foo", 1) + od.add("Foo", 2) + od.add("bar", 3) + del od["foo"] + assert len(od.lst) == 2 + + def test_replace(self): + od = odict.ODict() + od.add("one", "two") + od.add("two", "one") + assert od.replace("one", "vun") == 2 + assert od.lst == [ + ["vun", "two"], + ["two", "vun"], + ] + + def test_get(self): + od = odict.ODict() + od.add("one", "two") + assert od.get("one") == ["two"] + assert od.get("two") is None + + def test_get_first(self): + od = odict.ODict() + od.add("one", "two") + od.add("one", "three") + assert od.get_first("one") == "two" + assert od.get_first("two") is None + + def test_extend(self): + a = odict.ODict([["a", "b"], ["c", "d"]]) + b = odict.ODict([["a", "b"], ["e", "f"]]) + a.extend(b) + assert len(a) == 4 + assert a["a"] == ["b", "b"] + + +class TestODictCaseless(object): + + def test_override(self): + o = odict.ODictCaseless() + o.add('T', 'application/x-www-form-urlencoded; charset=UTF-8') + o["T"] = ["foo"] + assert o["T"] == ["foo"] + + def test_case_preservation(self): + od = odict.ODictCaseless() + od["Foo"] = ["1"] + assert "foo" in od + assert od.items()[0][0] == "Foo" + assert od.get("foo") == ["1"] + assert od.get("foo", [""]) == ["1"] + assert od.get("Foo", [""]) == ["1"] + assert od.get("xx", "yy") == "yy" + + def test_del(self): + od = odict.ODictCaseless() + od.add("foo", 1) + od.add("Foo", 2) + od.add("bar", 3) + del od["foo"] + assert len(od) == 1 + + def test_keys(self): + od = odict.ODictCaseless() + assert not od.keys() + od.add("foo", 1) + assert od.keys() == ["foo"] + od.add("Foo", 2) + assert od.keys() == ["foo"] + od.add("bar", 2) + assert len(od.keys()) == 2 + + def test_add_order(self): + od = odict.ODict( + [ + ["one", "uno"], + ["two", "due"], + ["three", "tre"], + ] + ) + od["two"] = ["foo", "bar"] + assert od.lst == [ + ["one", "uno"], + ["two", "foo"], + ["three", "tre"], + ["two", "bar"], + ] diff --git a/test/netlib/test_socks.py b/test/netlib/test_socks.py new file mode 100644 index 00000000..d95dee41 --- /dev/null +++ b/test/netlib/test_socks.py @@ -0,0 +1,149 @@ +import ipaddress +from io import BytesIO +import socket +from netlib import socks, tcp, tutils + + +def test_client_greeting(): + raw = tutils.treader(b"\x05\x02\x00\xBE\xEF") + out = BytesIO() + msg = socks.ClientGreeting.from_file(raw) + msg.assert_socks5() + msg.to_file(out) + + assert out.getvalue() == raw.getvalue()[:-1] + assert msg.ver == 5 + assert len(msg.methods) == 2 + assert 0xBE in msg.methods + assert 0xEF not in msg.methods + + +def test_client_greeting_assert_socks5(): + raw = tutils.treader(b"\x00\x00") + msg = socks.ClientGreeting.from_file(raw) + tutils.raises(socks.SocksError, msg.assert_socks5) + + raw = tutils.treader(b"HTTP/1.1 200 OK" + b" " * 100) + msg = socks.ClientGreeting.from_file(raw) + try: + msg.assert_socks5() + except socks.SocksError as e: + assert "Invalid SOCKS version" in str(e) + assert "HTTP" not in str(e) + else: + assert False + + raw = tutils.treader(b"GET / HTTP/1.1" + b" " * 100) + msg = socks.ClientGreeting.from_file(raw) + try: + msg.assert_socks5() + except socks.SocksError as e: + assert "Invalid SOCKS version" in str(e) + assert "HTTP" in str(e) + else: + assert False + + raw = tutils.treader(b"XX") + tutils.raises( + socks.SocksError, + socks.ClientGreeting.from_file, + raw, + fail_early=True) + + +def test_server_greeting(): + raw = tutils.treader(b"\x05\x02") + out = BytesIO() + msg = socks.ServerGreeting.from_file(raw) + msg.assert_socks5() + msg.to_file(out) + + assert out.getvalue() == raw.getvalue() + assert msg.ver == 5 + assert msg.method == 0x02 + + +def test_server_greeting_assert_socks5(): + raw = tutils.treader(b"HTTP/1.1 200 OK" + b" " * 100) + msg = socks.ServerGreeting.from_file(raw) + try: + msg.assert_socks5() + except socks.SocksError as e: + assert "Invalid SOCKS version" in str(e) + assert "HTTP" in str(e) + else: + assert False + + raw = tutils.treader(b"GET / HTTP/1.1" + b" " * 100) + msg = socks.ServerGreeting.from_file(raw) + try: + msg.assert_socks5() + except socks.SocksError as e: + assert "Invalid SOCKS version" in str(e) + assert "HTTP" not in str(e) + else: + assert False + + +def test_message(): + raw = tutils.treader(b"\x05\x01\x00\x03\x0bexample.com\xDE\xAD\xBE\xEF") + out = BytesIO() + msg = socks.Message.from_file(raw) + msg.assert_socks5() + assert raw.read(2) == b"\xBE\xEF" + msg.to_file(out) + + assert out.getvalue() == raw.getvalue()[:-2] + assert msg.ver == 5 + assert msg.msg == 0x01 + assert msg.atyp == 0x03 + assert msg.addr == ("example.com", 0xDEAD) + + +def test_message_assert_socks5(): + raw = tutils.treader(b"\xEE\x01\x00\x03\x0bexample.com\xDE\xAD\xBE\xEF") + msg = socks.Message.from_file(raw) + tutils.raises(socks.SocksError, msg.assert_socks5) + + +def test_message_ipv4(): + # Test ATYP=0x01 (IPV4) + raw = tutils.treader(b"\x05\x01\x00\x01\x7f\x00\x00\x01\xDE\xAD\xBE\xEF") + out = BytesIO() + msg = socks.Message.from_file(raw) + left = raw.read(2) + assert left == b"\xBE\xEF" + msg.to_file(out) + + assert out.getvalue() == raw.getvalue()[:-2] + assert msg.addr == ("127.0.0.1", 0xDEAD) + + +def test_message_ipv6(): + # Test ATYP=0x04 (IPV6) + ipv6_addr = u"2001:db8:85a3:8d3:1319:8a2e:370:7344" + + raw = tutils.treader( + b"\x05\x01\x00\x04" + + ipaddress.IPv6Address(ipv6_addr).packed + + b"\xDE\xAD\xBE\xEF") + out = BytesIO() + msg = socks.Message.from_file(raw) + assert raw.read(2) == b"\xBE\xEF" + msg.to_file(out) + + assert out.getvalue() == raw.getvalue()[:-2] + assert msg.addr.host == ipv6_addr + + +def test_message_invalid_rsv(): + raw = tutils.treader(b"\x05\x01\xFF\x01\x7f\x00\x00\x01\xDE\xAD\xBE\xEF") + tutils.raises(socks.SocksError, socks.Message.from_file, raw) + + +def test_message_unknown_atyp(): + raw = tutils.treader(b"\x05\x02\x00\x02\x7f\x00\x00\x01\xDE\xAD\xBE\xEF") + tutils.raises(socks.SocksError, socks.Message.from_file, raw) + + m = socks.Message(5, 1, 0x02, tcp.Address(("example.com", 5050))) + tutils.raises(socks.SocksError, m.to_file, BytesIO()) diff --git a/test/netlib/test_tcp.py b/test/netlib/test_tcp.py new file mode 100644 index 00000000..8ae3aa51 --- /dev/null +++ b/test/netlib/test_tcp.py @@ -0,0 +1,795 @@ +from io import BytesIO +from six.moves import queue +import time +import socket +import random +import os +import threading +import mock + +from OpenSSL import SSL +import OpenSSL + +from netlib import tcp, certutils, tutils, tservers +from netlib.exceptions import InvalidCertificateException, TcpReadIncomplete, TlsException, \ + TcpTimeout, TcpDisconnect, TcpException, NetlibException + + +class EchoHandler(tcp.BaseHandler): + sni = None + + def handle_sni(self, connection): + self.sni = connection.get_servername() + + def handle(self): + v = self.rfile.readline() + self.wfile.write(v) + self.wfile.flush() + + +class ClientCipherListHandler(tcp.BaseHandler): + sni = None + + def handle(self): + self.wfile.write("%s" % self.connection.get_cipher_list()) + self.wfile.flush() + + +class HangHandler(tcp.BaseHandler): + + def handle(self): + while True: + time.sleep(1) + + +class ALPNHandler(tcp.BaseHandler): + sni = None + + def handle(self): + alp = self.get_alpn_proto_negotiated() + if alp: + self.wfile.write(alp) + else: + self.wfile.write(b"NONE") + self.wfile.flush() + + +class TestServer(tservers.ServerTestBase): + handler = EchoHandler + + def test_echo(self): + testval = b"echo!\n" + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + + def test_thread_start_error(self): + with mock.patch.object(threading.Thread, "start", side_effect=threading.ThreadError("nonewthread")) as m: + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + assert not c.rfile.read(1) + assert m.called + assert "nonewthread" in self.q.get_nowait() + self.test_echo() + + +class TestServerBind(tservers.ServerTestBase): + + class handler(tcp.BaseHandler): + + def handle(self): + self.wfile.write(str(self.connection.getpeername()).encode()) + self.wfile.flush() + + def test_bind(self): + """ Test to bind to a given random port. Try again if the random port turned out to be blocked. """ + for i in range(20): + random_port = random.randrange(1024, 65535) + try: + c = tcp.TCPClient( + ("127.0.0.1", self.port), source_address=( + "127.0.0.1", random_port)) + c.connect() + assert c.rfile.readline() == str(("127.0.0.1", random_port)).encode() + return + except TcpException: # port probably already in use + pass + + +class TestServerIPv6(tservers.ServerTestBase): + handler = EchoHandler + addr = tcp.Address(("localhost", 0), use_ipv6=True) + + def test_echo(self): + testval = b"echo!\n" + c = tcp.TCPClient(tcp.Address(("::1", self.port), use_ipv6=True)) + c.connect() + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + + +class TestEcho(tservers.ServerTestBase): + handler = EchoHandler + + def test_echo(self): + testval = b"echo!\n" + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + + +class HardDisconnectHandler(tcp.BaseHandler): + + def handle(self): + self.connection.close() + + +class TestFinishFail(tservers.ServerTestBase): + + """ + This tests a difficult-to-trigger exception in the .finish() method of + the handler. + """ + handler = EchoHandler + + def test_disconnect_in_finish(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.wfile.write(b"foo\n") + c.wfile.flush = mock.Mock(side_effect=TcpDisconnect) + c.finish() + + +class TestServerSSL(tservers.ServerTestBase): + handler = EchoHandler + ssl = dict( + cipher_list="AES256-SHA", + chain_file=tutils.test_data.path("data/server.crt") + ) + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl(sni=b"foo.com", options=SSL.OP_ALL) + testval = b"echo!\n" + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + + def test_get_current_cipher(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + assert not c.get_current_cipher() + c.convert_to_ssl(sni=b"foo.com") + ret = c.get_current_cipher() + assert ret + assert "AES" in ret[0] + + +class TestSSLv3Only(tservers.ServerTestBase): + handler = EchoHandler + ssl = dict( + request_client_cert=False, + v3_only=True + ) + + def test_failure(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + tutils.raises(TlsException, c.convert_to_ssl, sni=b"foo.com") + + +class TestSSLUpstreamCertVerificationWBadServerCert(tservers.ServerTestBase): + handler = EchoHandler + + ssl = dict( + cert=tutils.test_data.path("data/verificationcerts/self-signed.crt"), + key=tutils.test_data.path("data/verificationcerts/self-signed.key") + ) + + def test_mode_default_should_pass(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + + c.convert_to_ssl() + + # Verification errors should be saved even if connection isn't aborted + # aborted + assert c.ssl_verification_error is not None + + testval = b"echo!\n" + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + + def test_mode_none_should_pass(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + + c.convert_to_ssl(verify_options=SSL.VERIFY_NONE) + + # Verification errors should be saved even if connection isn't aborted + assert c.ssl_verification_error is not None + + testval = b"echo!\n" + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + + def test_mode_strict_should_fail(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + + with tutils.raises(InvalidCertificateException): + c.convert_to_ssl( + sni=b"example.mitmproxy.org", + verify_options=SSL.VERIFY_PEER, + ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted-root.crt") + ) + + assert c.ssl_verification_error is not None + + # Unknown issuing certificate authority for first certificate + assert c.ssl_verification_error['errno'] == 18 + assert c.ssl_verification_error['depth'] == 0 + + +class TestSSLUpstreamCertVerificationWBadHostname(tservers.ServerTestBase): + handler = EchoHandler + + ssl = dict( + cert=tutils.test_data.path("data/verificationcerts/trusted-leaf.crt"), + key=tutils.test_data.path("data/verificationcerts/trusted-leaf.key") + ) + + def test_should_fail_without_sni(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + + with tutils.raises(TlsException): + c.convert_to_ssl( + verify_options=SSL.VERIFY_PEER, + ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted-root.crt") + ) + + def test_should_fail(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + + with tutils.raises(InvalidCertificateException): + c.convert_to_ssl( + sni=b"mitmproxy.org", + verify_options=SSL.VERIFY_PEER, + ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted-root.crt") + ) + + assert c.ssl_verification_error is not None + + +class TestSSLUpstreamCertVerificationWValidCertChain(tservers.ServerTestBase): + handler = EchoHandler + + ssl = dict( + cert=tutils.test_data.path("data/verificationcerts/trusted-leaf.crt"), + key=tutils.test_data.path("data/verificationcerts/trusted-leaf.key") + ) + + def test_mode_strict_w_pemfile_should_pass(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + + c.convert_to_ssl( + sni=b"example.mitmproxy.org", + verify_options=SSL.VERIFY_PEER, + ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted-root.crt") + ) + + assert c.ssl_verification_error is None + + testval = b"echo!\n" + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + + def test_mode_strict_w_cadir_should_pass(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + + c.convert_to_ssl( + sni=b"example.mitmproxy.org", + verify_options=SSL.VERIFY_PEER, + ca_path=tutils.test_data.path("data/verificationcerts/") + ) + + assert c.ssl_verification_error is None + + testval = b"echo!\n" + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + + +class TestSSLClientCert(tservers.ServerTestBase): + + class handler(tcp.BaseHandler): + sni = None + + def handle_sni(self, connection): + self.sni = connection.get_servername() + + def handle(self): + self.wfile.write(b"%d\n" % self.clientcert.serial) + self.wfile.flush() + + ssl = dict( + request_client_cert=True, + v3_only=False + ) + + def test_clientcert(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl( + cert=tutils.test_data.path("data/clientcert/client.pem")) + assert c.rfile.readline().strip() == b"1" + + def test_clientcert_err(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + tutils.raises( + TlsException, + c.convert_to_ssl, + cert=tutils.test_data.path("data/clientcert/make") + ) + + +class TestSNI(tservers.ServerTestBase): + + class handler(tcp.BaseHandler): + sni = None + + def handle_sni(self, connection): + self.sni = connection.get_servername() + + def handle(self): + self.wfile.write(self.sni) + self.wfile.flush() + + ssl = True + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl(sni=b"foo.com") + assert c.sni == b"foo.com" + assert c.rfile.readline() == b"foo.com" + + +class TestServerCipherList(tservers.ServerTestBase): + handler = ClientCipherListHandler + ssl = dict( + cipher_list='RC4-SHA' + ) + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl(sni=b"foo.com") + assert c.rfile.readline() == b"['RC4-SHA']" + + +class TestServerCurrentCipher(tservers.ServerTestBase): + + class handler(tcp.BaseHandler): + sni = None + + def handle(self): + self.wfile.write(str(self.get_current_cipher()).encode()) + self.wfile.flush() + + ssl = dict( + cipher_list='RC4-SHA' + ) + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl(sni=b"foo.com") + assert b"RC4-SHA" in c.rfile.readline() + + +class TestServerCipherListError(tservers.ServerTestBase): + handler = ClientCipherListHandler + ssl = dict( + cipher_list='bogus' + ) + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + tutils.raises("handshake error", c.convert_to_ssl, sni=b"foo.com") + + +class TestClientCipherListError(tservers.ServerTestBase): + handler = ClientCipherListHandler + ssl = dict( + cipher_list='RC4-SHA' + ) + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + tutils.raises( + "cipher specification", + c.convert_to_ssl, + sni=b"foo.com", + cipher_list="bogus") + + +class TestSSLDisconnect(tservers.ServerTestBase): + + class handler(tcp.BaseHandler): + + def handle(self): + self.finish() + + ssl = True + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + # Excercise SSL.ZeroReturnError + c.rfile.read(10) + c.close() + tutils.raises(TcpDisconnect, c.wfile.write, b"foo") + tutils.raises(queue.Empty, self.q.get_nowait) + + +class TestSSLHardDisconnect(tservers.ServerTestBase): + handler = HardDisconnectHandler + ssl = True + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + # Exercise SSL.SysCallError + c.rfile.read(10) + c.close() + tutils.raises(TcpDisconnect, c.wfile.write, b"foo") + + +class TestDisconnect(tservers.ServerTestBase): + + def test_echo(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.rfile.read(10) + c.wfile.write(b"foo") + c.close() + c.close() + + +class TestServerTimeOut(tservers.ServerTestBase): + + class handler(tcp.BaseHandler): + + def handle(self): + self.timeout = False + self.settimeout(0.01) + try: + self.rfile.read(10) + except TcpTimeout: + self.timeout = True + + def test_timeout(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + time.sleep(0.3) + assert self.last_handler.timeout + + +class TestTimeOut(tservers.ServerTestBase): + handler = HangHandler + + def test_timeout(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.settimeout(0.1) + assert c.gettimeout() == 0.1 + tutils.raises(TcpTimeout, c.rfile.read, 10) + + +class TestALPNClient(tservers.ServerTestBase): + handler = ALPNHandler + ssl = dict( + alpn_select=b"bar" + ) + + if tcp.HAS_ALPN: + def test_alpn(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl(alpn_protos=[b"foo", b"bar", b"fasel"]) + assert c.get_alpn_proto_negotiated() == b"bar" + assert c.rfile.readline().strip() == b"bar" + + def test_no_alpn(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + assert c.get_alpn_proto_negotiated() == b"" + assert c.rfile.readline().strip() == b"NONE" + + else: + def test_none_alpn(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl(alpn_protos=[b"foo", b"bar", b"fasel"]) + assert c.get_alpn_proto_negotiated() == b"" + assert c.rfile.readline() == b"NONE" + + +class TestNoSSLNoALPNClient(tservers.ServerTestBase): + handler = ALPNHandler + + def test_no_ssl_no_alpn(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + assert c.get_alpn_proto_negotiated() == b"" + assert c.rfile.readline().strip() == b"NONE" + + +class TestSSLTimeOut(tservers.ServerTestBase): + handler = HangHandler + ssl = True + + def test_timeout_client(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + c.settimeout(0.1) + tutils.raises(TcpTimeout, c.rfile.read, 10) + + +class TestDHParams(tservers.ServerTestBase): + handler = HangHandler + ssl = dict( + dhparams=certutils.CertStore.load_dhparam( + tutils.test_data.path("data/dhparam.pem"), + ), + cipher_list="DHE-RSA-AES256-SHA" + ) + + def test_dhparams(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + ret = c.get_current_cipher() + assert ret[0] == "DHE-RSA-AES256-SHA" + + def test_create_dhparams(self): + with tutils.tmpdir() as d: + filename = os.path.join(d, "dhparam.pem") + certutils.CertStore.load_dhparam(filename) + assert os.path.exists(filename) + + +class TestTCPClient: + + def test_conerr(self): + c = tcp.TCPClient(("127.0.0.1", 0)) + tutils.raises(TcpException, c.connect) + + +class TestFileLike: + + def test_blocksize(self): + s = BytesIO(b"1234567890abcdefghijklmnopqrstuvwxyz") + s = tcp.Reader(s) + s.BLOCKSIZE = 2 + assert s.read(1) == b"1" + assert s.read(2) == b"23" + assert s.read(3) == b"456" + assert s.read(4) == b"7890" + d = s.read(-1) + assert d.startswith(b"abc") and d.endswith(b"xyz") + + def test_wrap(self): + s = BytesIO(b"foobar\nfoobar") + s.flush() + s = tcp.Reader(s) + assert s.readline() == b"foobar\n" + assert s.readline() == b"foobar" + # Test __getattr__ + assert s.isatty + + def test_limit(self): + s = BytesIO(b"foobar\nfoobar") + s = tcp.Reader(s) + assert s.readline(3) == b"foo" + + def test_limitless(self): + s = BytesIO(b"f" * (50 * 1024)) + s = tcp.Reader(s) + ret = s.read(-1) + assert len(ret) == 50 * 1024 + + def test_readlog(self): + s = BytesIO(b"foobar\nfoobar") + s = tcp.Reader(s) + assert not s.is_logging() + s.start_log() + assert s.is_logging() + s.readline() + assert s.get_log() == b"foobar\n" + s.read(1) + assert s.get_log() == b"foobar\nf" + s.start_log() + assert s.get_log() == b"" + s.read(1) + assert s.get_log() == b"o" + s.stop_log() + tutils.raises(ValueError, s.get_log) + + def test_writelog(self): + s = BytesIO() + s = tcp.Writer(s) + s.start_log() + assert s.is_logging() + s.write(b"x") + assert s.get_log() == b"x" + s.write(b"x") + assert s.get_log() == b"xx" + + def test_writer_flush_error(self): + s = BytesIO() + s = tcp.Writer(s) + o = mock.MagicMock() + o.flush = mock.MagicMock(side_effect=socket.error) + s.o = o + tutils.raises(TcpDisconnect, s.flush) + + def test_reader_read_error(self): + s = BytesIO(b"foobar\nfoobar") + s = tcp.Reader(s) + o = mock.MagicMock() + o.read = mock.MagicMock(side_effect=socket.error) + s.o = o + tutils.raises(TcpDisconnect, s.read, 10) + + def test_reset_timestamps(self): + s = BytesIO(b"foobar\nfoobar") + s = tcp.Reader(s) + s.first_byte_timestamp = 500 + s.reset_timestamps() + assert not s.first_byte_timestamp + + def test_first_byte_timestamp_updated_on_read(self): + s = BytesIO(b"foobar\nfoobar") + s = tcp.Reader(s) + s.read(1) + assert s.first_byte_timestamp + expected = s.first_byte_timestamp + s.read(5) + assert s.first_byte_timestamp == expected + + def test_first_byte_timestamp_updated_on_readline(self): + s = BytesIO(b"foobar\nfoobar\nfoobar") + s = tcp.Reader(s) + s.readline() + assert s.first_byte_timestamp + expected = s.first_byte_timestamp + s.readline() + assert s.first_byte_timestamp == expected + + def test_read_ssl_error(self): + s = mock.MagicMock() + s.read = mock.MagicMock(side_effect=SSL.Error()) + s = tcp.Reader(s) + tutils.raises(TlsException, s.read, 1) + + def test_read_syscall_ssl_error(self): + s = mock.MagicMock() + s.read = mock.MagicMock(side_effect=SSL.SysCallError()) + s = tcp.Reader(s) + tutils.raises(TlsException, s.read, 1) + + def test_reader_readline_disconnect(self): + o = mock.MagicMock() + o.read = mock.MagicMock(side_effect=socket.error) + s = tcp.Reader(o) + tutils.raises(TcpDisconnect, s.readline, 10) + + def test_reader_incomplete_error(self): + s = BytesIO(b"foobar") + s = tcp.Reader(s) + tutils.raises(TcpReadIncomplete, s.safe_read, 10) + + +class TestPeek(tservers.ServerTestBase): + handler = EchoHandler + + def _connect(self, c): + c.connect() + + def test_peek(self): + testval = b"peek!\n" + c = tcp.TCPClient(("127.0.0.1", self.port)) + self._connect(c) + c.wfile.write(testval) + c.wfile.flush() + + assert c.rfile.peek(4) == b"peek" + assert c.rfile.peek(6) == b"peek!\n" + assert c.rfile.readline() == testval + + c.close() + with tutils.raises(NetlibException): + if c.rfile.peek(1) == b"": + # Workaround for Python 2 on Unix: + # Peeking a closed connection does not raise an exception here. + raise NetlibException() + + +class TestPeekSSL(TestPeek): + ssl = True + + def _connect(self, c): + c.connect() + c.convert_to_ssl() + + +class TestAddress: + + def test_simple(self): + a = tcp.Address("localhost", True) + assert a.use_ipv6 + b = tcp.Address("foo.com", True) + assert not a == b + assert str(b) == str(tuple("foo.com")) + c = tcp.Address("localhost", True) + assert a == c + assert not a != c + assert repr(a) + + +class TestSSLKeyLogger(tservers.ServerTestBase): + handler = EchoHandler + ssl = dict( + cipher_list="AES256-SHA" + ) + + def test_log(self): + testval = b"echo!\n" + _logfun = tcp.log_ssl_key + + with tutils.tmpdir() as d: + logfile = os.path.join(d, "foo", "bar", "logfile") + tcp.log_ssl_key = tcp.SSLKeyLogger(logfile) + + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + c.convert_to_ssl() + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + c.finish() + + tcp.log_ssl_key.close() + with open(logfile, "rb") as f: + assert f.read().count(b"CLIENT_RANDOM") == 2 + + tcp.log_ssl_key = _logfun + + def test_create_logfun(self): + assert isinstance( + tcp.SSLKeyLogger.create_logfun("test"), + tcp.SSLKeyLogger) + assert not tcp.SSLKeyLogger.create_logfun(False) diff --git a/test/netlib/test_utils.py b/test/netlib/test_utils.py new file mode 100644 index 00000000..b096e5bc --- /dev/null +++ b/test/netlib/test_utils.py @@ -0,0 +1,141 @@ +from netlib import utils, tutils +from netlib.http import Headers + +def test_bidi(): + b = utils.BiDi(a=1, b=2) + assert b.a == 1 + assert b.get_name(1) == "a" + assert b.get_name(5) is None + tutils.raises(AttributeError, getattr, b, "c") + tutils.raises(ValueError, utils.BiDi, one=1, two=1) + + +def test_hexdump(): + assert list(utils.hexdump(b"one\0" * 10)) + + +def test_clean_bin(): + assert utils.clean_bin(b"one") == b"one" + assert utils.clean_bin(b"\00ne") == b".ne" + assert utils.clean_bin(b"\nne") == b"\nne" + assert utils.clean_bin(b"\nne", False) == b".ne" + assert utils.clean_bin(u"\u2605".encode("utf8")) == b"..." + + assert utils.clean_bin(u"one") == u"one" + assert utils.clean_bin(u"\00ne") == u".ne" + assert utils.clean_bin(u"\nne") == u"\nne" + assert utils.clean_bin(u"\nne", False) == u".ne" + assert utils.clean_bin(u"\u2605") == u"\u2605" + + +def test_pretty_size(): + assert utils.pretty_size(100) == "100B" + assert utils.pretty_size(1024) == "1kB" + assert utils.pretty_size(1024 + (1024 / 2.0)) == "1.5kB" + assert utils.pretty_size(1024 * 1024) == "1MB" + + +def test_parse_url(): + with tutils.raises(ValueError): + utils.parse_url("") + + s, h, po, pa = utils.parse_url(b"http://foo.com:8888/test") + assert s == b"http" + assert h == b"foo.com" + assert po == 8888 + assert pa == b"/test" + + s, h, po, pa = utils.parse_url("http://foo/bar") + assert s == b"http" + assert h == b"foo" + assert po == 80 + assert pa == b"/bar" + + s, h, po, pa = utils.parse_url(b"http://user:pass@foo/bar") + assert s == b"http" + assert h == b"foo" + assert po == 80 + assert pa == b"/bar" + + s, h, po, pa = utils.parse_url(b"http://foo") + assert pa == b"/" + + s, h, po, pa = utils.parse_url(b"https://foo") + assert po == 443 + + with tutils.raises(ValueError): + utils.parse_url(b"https://foo:bar") + + # Invalid IDNA + with tutils.raises(ValueError): + utils.parse_url("http://\xfafoo") + # Invalid PATH + with tutils.raises(ValueError): + utils.parse_url("http:/\xc6/localhost:56121") + # Null byte in host + with tutils.raises(ValueError): + utils.parse_url("http://foo\0") + # Port out of range + _, _, port, _ = utils.parse_url("http://foo:999999") + assert port == 80 + # Invalid IPv6 URL - see http://www.ietf.org/rfc/rfc2732.txt + with tutils.raises(ValueError): + utils.parse_url('http://lo[calhost') + + +def test_unparse_url(): + assert utils.unparse_url("http", "foo.com", 99, "") == "http://foo.com:99" + assert utils.unparse_url("http", "foo.com", 80, "/bar") == "http://foo.com/bar" + assert utils.unparse_url("https", "foo.com", 80, "") == "https://foo.com:80" + assert utils.unparse_url("https", "foo.com", 443, "") == "https://foo.com" + + +def test_urlencode(): + assert utils.urlencode([('foo', 'bar')]) + + +def test_urldecode(): + s = "one=two&three=four" + assert len(utils.urldecode(s)) == 2 + + +def test_get_header_tokens(): + headers = Headers() + assert utils.get_header_tokens(headers, "foo") == [] + headers["foo"] = "bar" + assert utils.get_header_tokens(headers, "foo") == ["bar"] + headers["foo"] = "bar, voing" + assert utils.get_header_tokens(headers, "foo") == ["bar", "voing"] + headers.set_all("foo", ["bar, voing", "oink"]) + assert utils.get_header_tokens(headers, "foo") == ["bar", "voing", "oink"] + + +def test_multipartdecode(): + boundary = 'somefancyboundary' + headers = Headers( + content_type='multipart/form-data; boundary=' + boundary + ) + content = ( + "--{0}\n" + "Content-Disposition: form-data; name=\"field1\"\n\n" + "value1\n" + "--{0}\n" + "Content-Disposition: form-data; name=\"field2\"\n\n" + "value2\n" + "--{0}--".format(boundary).encode() + ) + + form = utils.multipartdecode(headers, content) + + assert len(form) == 2 + assert form[0] == (b"field1", b"value1") + assert form[1] == (b"field2", b"value2") + + +def test_parse_content_type(): + p = utils.parse_content_type + assert p("text/html") == ("text", "html", {}) + assert p("text") is None + + v = p("text/html; charset=UTF-8") + assert v == ('text', 'html', {'charset': 'UTF-8'}) diff --git a/test/netlib/test_version_check.py b/test/netlib/test_version_check.py new file mode 100644 index 00000000..ec2396fe --- /dev/null +++ b/test/netlib/test_version_check.py @@ -0,0 +1,38 @@ +from io import StringIO +import mock +from netlib import version_check, version + + +@mock.patch("sys.exit") +def test_check_mitmproxy_version(sexit): + fp = StringIO() + version_check.check_mitmproxy_version(version.IVERSION, fp=fp) + assert not fp.getvalue() + assert not sexit.called + + b = (version.IVERSION[0] - 1, version.IVERSION[1]) + version_check.check_mitmproxy_version(b, fp=fp) + assert fp.getvalue() + assert sexit.called + + +@mock.patch("sys.exit") +def test_check_pyopenssl_version(sexit): + fp = StringIO() + version_check.check_pyopenssl_version(fp=fp) + assert not fp.getvalue() + assert not sexit.called + + version_check.check_pyopenssl_version((9999,), fp=fp) + assert "outdated" in fp.getvalue() + assert sexit.called + + +@mock.patch("sys.exit") +@mock.patch("OpenSSL.__version__") +def test_unparseable_pyopenssl_version(version, sexit): + version.split.return_value = ["foo", "bar"] + fp = StringIO() + version_check.check_pyopenssl_version(fp=fp) + assert "Cannot parse" in fp.getvalue() + assert not sexit.called diff --git a/test/netlib/test_wsgi.py b/test/netlib/test_wsgi.py new file mode 100644 index 00000000..8c782b27 --- /dev/null +++ b/test/netlib/test_wsgi.py @@ -0,0 +1,106 @@ +from io import BytesIO +import sys +from netlib import wsgi +from netlib.http import Headers + + +def tflow(): + headers = Headers(test=b"value") + req = wsgi.Request("http", "GET", "/", "HTTP/1.1", headers, "") + return wsgi.Flow(("127.0.0.1", 8888), req) + + +class ExampleApp: + + def __init__(self): + self.called = False + + def __call__(self, environ, start_response): + self.called = True + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return [b'Hello', b' world!\n'] + + +class TestWSGI: + + def test_make_environ(self): + w = wsgi.WSGIAdaptor(None, "foo", 80, "version") + tf = tflow() + assert w.make_environ(tf, None) + + tf.request.path = "/foo?bar=voing" + r = w.make_environ(tf, None) + assert r["QUERY_STRING"] == "bar=voing" + + def test_serve(self): + ta = ExampleApp() + w = wsgi.WSGIAdaptor(ta, "foo", 80, "version") + f = tflow() + f.request.host = "foo" + f.request.port = 80 + + wfile = BytesIO() + err = w.serve(f, wfile) + assert ta.called + assert not err + + val = wfile.getvalue() + assert b"Hello world" in val + assert b"Server:" in val + + def _serve(self, app): + w = wsgi.WSGIAdaptor(app, "foo", 80, "version") + f = tflow() + f.request.host = "foo" + f.request.port = 80 + wfile = BytesIO() + w.serve(f, wfile) + return wfile.getvalue() + + def test_serve_empty_body(self): + def app(environ, start_response): + status = '200 OK' + response_headers = [('Foo', 'bar')] + start_response(status, response_headers) + return [] + assert self._serve(app) + + def test_serve_double_start(self): + def app(environ, start_response): + try: + raise ValueError("foo") + except: + sys.exc_info() + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + start_response(status, response_headers) + assert b"Internal Server Error" in self._serve(app) + + def test_serve_single_err(self): + def app(environ, start_response): + try: + raise ValueError("foo") + except: + ei = sys.exc_info() + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers, ei) + yield b"" + assert b"Internal Server Error" in self._serve(app) + + def test_serve_double_err(self): + def app(environ, start_response): + try: + raise ValueError("foo") + except: + ei = sys.exc_info() + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + yield b"aaa" + start_response(status, response_headers, ei) + yield b"bbb" + assert b"Internal Server Error" in self._serve(app) diff --git a/test/netlib/tools/getcertnames b/test/netlib/tools/getcertnames new file mode 100644 index 00000000..e33619f7 --- /dev/null +++ b/test/netlib/tools/getcertnames @@ -0,0 +1,27 @@ +#!/usr/bin/env python +import sys +sys.path.insert(0, "../../") +from netlib import tcp + + +def get_remote_cert(host, port, sni): + c = tcp.TCPClient((host, port)) + c.connect() + c.convert_to_ssl(sni=sni) + return c.cert + +if len(sys.argv) > 2: + port = int(sys.argv[2]) +else: + port = 443 +if len(sys.argv) > 3: + sni = sys.argv[3] +else: + sni = None + +cert = get_remote_cert(sys.argv[1], port, sni) +print "CN:", cert.cn +if cert.altnames: + print "SANs:", + for i in cert.altnames: + print "\t", i diff --git a/test/netlib/websockets/__init__.py b/test/netlib/websockets/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/netlib/websockets/__init__.py diff --git a/test/netlib/websockets/test_websockets.py b/test/netlib/websockets/test_websockets.py new file mode 100644 index 00000000..d53f0d83 --- /dev/null +++ b/test/netlib/websockets/test_websockets.py @@ -0,0 +1,266 @@ +import os + +from netlib.http.http1 import read_response, read_request + +from netlib import tcp, websockets, http, tutils, tservers +from netlib.http import status_codes +from netlib.tutils import treq + +from netlib.exceptions import * + + +class WebSocketsEchoHandler(tcp.BaseHandler): + + def __init__(self, connection, address, server): + super(WebSocketsEchoHandler, self).__init__( + connection, address, server + ) + self.protocol = websockets.WebsocketsProtocol() + self.handshake_done = False + + def handle(self): + while True: + if not self.handshake_done: + self.handshake() + else: + self.read_next_message() + + def read_next_message(self): + frame = websockets.Frame.from_file(self.rfile) + self.on_message(frame.payload) + + def send_message(self, message): + frame = websockets.Frame.default(message, from_client=False) + frame.to_file(self.wfile) + + def handshake(self): + + req = read_request(self.rfile) + key = self.protocol.check_client_handshake(req.headers) + + preamble = 'HTTP/1.1 101 %s' % status_codes.RESPONSES.get(101) + self.wfile.write(preamble.encode() + b"\r\n") + headers = self.protocol.server_handshake_headers(key) + self.wfile.write(str(headers) + "\r\n") + self.wfile.flush() + self.handshake_done = True + + def on_message(self, message): + if message is not None: + self.send_message(message) + + +class WebSocketsClient(tcp.TCPClient): + + def __init__(self, address, source_address=None): + super(WebSocketsClient, self).__init__(address, source_address) + self.protocol = websockets.WebsocketsProtocol() + self.client_nonce = None + + def connect(self): + super(WebSocketsClient, self).connect() + + preamble = b'GET / HTTP/1.1' + self.wfile.write(preamble + b"\r\n") + headers = self.protocol.client_handshake_headers() + self.client_nonce = headers["sec-websocket-key"].encode("ascii") + self.wfile.write(bytes(headers) + b"\r\n") + self.wfile.flush() + + resp = read_response(self.rfile, treq(method=b"GET")) + server_nonce = self.protocol.check_server_handshake(resp.headers) + + if not server_nonce == self.protocol.create_server_nonce(self.client_nonce): + self.close() + + def read_next_message(self): + return websockets.Frame.from_file(self.rfile).payload + + def send_message(self, message): + frame = websockets.Frame.default(message, from_client=True) + frame.to_file(self.wfile) + + +class TestWebSockets(tservers.ServerTestBase): + handler = WebSocketsEchoHandler + + def __init__(self): + self.protocol = websockets.WebsocketsProtocol() + + def random_bytes(self, n=100): + return os.urandom(n) + + def echo(self, msg): + client = WebSocketsClient(("127.0.0.1", self.port)) + client.connect() + client.send_message(msg) + response = client.read_next_message() + assert response == msg + + def test_simple_echo(self): + self.echo(b"hello I'm the client") + + def test_frame_sizes(self): + # length can fit in the the 7 bit payload length + small_msg = self.random_bytes(100) + # 50kb, sligthly larger than can fit in a 7 bit int + medium_msg = self.random_bytes(50000) + # 150kb, slightly larger than can fit in a 16 bit int + large_msg = self.random_bytes(150000) + + self.echo(small_msg) + self.echo(medium_msg) + self.echo(large_msg) + + def test_default_builder(self): + """ + default builder should always generate valid frames + """ + msg = self.random_bytes() + client_frame = websockets.Frame.default(msg, from_client=True) + server_frame = websockets.Frame.default(msg, from_client=False) + + def test_serialization_bijection(self): + """ + Ensure that various frame types can be serialized/deserialized back + and forth between to_bytes() and from_bytes() + """ + for is_client in [True, False]: + for num_bytes in [100, 50000, 150000]: + frame = websockets.Frame.default( + self.random_bytes(num_bytes), is_client + ) + frame2 = websockets.Frame.from_bytes( + frame.to_bytes() + ) + assert frame == frame2 + + bytes = b'\x81\x03cba' + assert websockets.Frame.from_bytes(bytes).to_bytes() == bytes + + def test_check_server_handshake(self): + headers = self.protocol.server_handshake_headers("key") + assert self.protocol.check_server_handshake(headers) + headers["Upgrade"] = "not_websocket" + assert not self.protocol.check_server_handshake(headers) + + def test_check_client_handshake(self): + headers = self.protocol.client_handshake_headers("key") + assert self.protocol.check_client_handshake(headers) == "key" + headers["Upgrade"] = "not_websocket" + assert not self.protocol.check_client_handshake(headers) + + +class BadHandshakeHandler(WebSocketsEchoHandler): + + def handshake(self): + + client_hs = read_request(self.rfile) + self.protocol.check_client_handshake(client_hs.headers) + + preamble = 'HTTP/1.1 101 %s\r\n' % status_codes.RESPONSES.get(101) + self.wfile.write(preamble.encode()) + headers = self.protocol.server_handshake_headers(b"malformed key") + self.wfile.write(bytes(headers) + b"\r\n") + self.wfile.flush() + self.handshake_done = True + + +class TestBadHandshake(tservers.ServerTestBase): + + """ + Ensure that the client disconnects if the server handshake is malformed + """ + handler = BadHandshakeHandler + + def test(self): + with tutils.raises(TcpDisconnect): + client = WebSocketsClient(("127.0.0.1", self.port)) + client.connect() + client.send_message(b"hello") + + +class TestFrameHeader: + + def test_roundtrip(self): + def round(*args, **kwargs): + f = websockets.FrameHeader(*args, **kwargs) + f2 = websockets.FrameHeader.from_file(tutils.treader(bytes(f))) + assert f == f2 + round() + round(fin=1) + round(rsv1=1) + round(rsv2=1) + round(rsv3=1) + round(payload_length=1) + round(payload_length=100) + round(payload_length=1000) + round(payload_length=10000) + round(opcode=websockets.OPCODE.PING) + round(masking_key=b"test") + + def test_human_readable(self): + f = websockets.FrameHeader( + masking_key=b"test", + fin=True, + payload_length=10 + ) + assert repr(f) + f = websockets.FrameHeader() + assert repr(f) + + def test_funky(self): + f = websockets.FrameHeader(masking_key=b"test", mask=False) + raw = bytes(f) + f2 = websockets.FrameHeader.from_file(tutils.treader(raw)) + assert not f2.mask + + def test_violations(self): + tutils.raises("opcode", websockets.FrameHeader, opcode=17) + tutils.raises("masking key", websockets.FrameHeader, masking_key=b"x") + + def test_automask(self): + f = websockets.FrameHeader(mask=True) + assert f.masking_key + + f = websockets.FrameHeader(masking_key=b"foob") + assert f.mask + + f = websockets.FrameHeader(masking_key=b"foob", mask=0) + assert not f.mask + assert f.masking_key + + +class TestFrame: + + def test_roundtrip(self): + def round(*args, **kwargs): + f = websockets.Frame(*args, **kwargs) + raw = bytes(f) + f2 = websockets.Frame.from_file(tutils.treader(raw)) + assert f == f2 + round(b"test") + round(b"test", fin=1) + round(b"test", rsv1=1) + round(b"test", opcode=websockets.OPCODE.PING) + round(b"test", masking_key=b"test") + + def test_human_readable(self): + f = websockets.Frame() + assert repr(f) + + +def test_masker(): + tests = [ + [b"a"], + [b"four"], + [b"fourf"], + [b"fourfive"], + [b"a", b"aasdfasdfa", b"asdf"], + [b"a" * 50, b"aasdfasdfa", b"asdf"], + ] + for i in tests: + m = websockets.Masker(b"abcd") + data = b"".join([m(t) for t in i]) + data2 = websockets.Masker(b"abcd")(data) + assert data2 == b"".join(i) diff --git a/test/pathod/data/clientcert/.gitignore b/test/pathod/data/clientcert/.gitignore new file mode 100644 index 00000000..07bc53d2 --- /dev/null +++ b/test/pathod/data/clientcert/.gitignore @@ -0,0 +1,3 @@ +client.crt +client.key +client.req diff --git a/test/pathod/data/clientcert/client.cnf b/test/pathod/data/clientcert/client.cnf new file mode 100644 index 00000000..5046a944 --- /dev/null +++ b/test/pathod/data/clientcert/client.cnf @@ -0,0 +1,5 @@ +[ ssl_client ] +basicConstraints = CA:FALSE +nsCertType = client +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth diff --git a/test/pathod/data/clientcert/client.pem b/test/pathod/data/clientcert/client.pem new file mode 100644 index 00000000..4927bca2 --- /dev/null +++ b/test/pathod/data/clientcert/client.pem @@ -0,0 +1,42 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0 +EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+ +ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G +3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/ +SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP +G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABAoIBAFE3FV/IDltbmHEP +iky93hbJm+6QgKepFReKpRVTyqb7LaygUvueQyPWQMIriKTsy675nxo8DQr7tQsO +y3YlSZgra/xNMikIB6e82c7K8DgyrDQw/rCqjZB3Xt4VCqsWJDLXnQMSn98lx0g7 +d7Lbf8soUpKWXqfdVpSDTi4fibSX6kshXyfSTpcz4AdoncEpViUfU1xkEEmZrjT8 +1GcCsDC41xdNmzCpqRuZX7DKSFRoB+0hUzsC1oiqM7FD5kixonRd4F5PbRXImIzt +6YCsT2okxTA04jX7yByis7LlOLTlkmLtKQYuc3erOFvwx89s4vW+AeFei+GGNitn +tHfSwbECgYEA7SzV+nN62hAERHlg8cEQT4TxnsWvbronYWcc/ev44eHSPDWL5tPi +GHfSbW6YAq5Wa0I9jMWfXyhOYEC3MZTC5EEeLOB71qVrTwcy/sY66rOrcgjFI76Q +5JFHQ4wy3SWU50KxE0oWJO9LIowprG+pW1vzqC3VF0T7q0FqESrY4LUCgYEA3F7Z +80ndnCUlooJAb+Hfotv7peFf1o6+m1PTRcz1lLnVt5R5lXj86kn+tXEpYZo1RiGR +2rE2N0seeznWCooakHcsBN7/qmFIhhooJNF7yW+JP2I4P2UV5+tJ+8bcs/voUkQD +1x+rGOuMn8nvHBd2+Vharft8eGL2mgooPVI2XusCgYEAlMZpO3+w8pTVeHaDP2MR +7i/AuQ3cbCLNjSX3Y7jgGCFllWspZRRIYXzYPNkA9b2SbBnTLjjRLgnEkFBIGgvs +7O2EFjaCuDRvydUEQhjq4ErwIsopj7B8h0QyZcbOKTbn3uFQ3n68wVJx2Sv/ADHT +FIHrp/WIE96r19Niy34LKXkCgYB2W59VsuOKnMz01l5DeR5C+0HSWxS9SReIl2IO +yEFSKullWyJeLIgyUaGy0990430feKI8whcrZXYumuah7IDN/KOwzhCk8vEfzWao +N7bzfqtJVrh9HA7C7DVlO+6H4JFrtcoWPZUIomJ549w/yz6EN3ckoMC+a/Ck1TW9 +ka1QFwKBgQCywG6TrZz0UmOjyLQZ+8Q4uvZklSW5NAKBkNnyuQ2kd5rzyYgMPE8C +Er8T88fdVIKvkhDyHhwcI7n58xE5Gr7wkwsrk/Hbd9/ZB2GgAPY3cATskK1v1McU +YeX38CU0fUS4aoy26hWQXkViB47IGQ3jWo3ZCtzIJl8DI9/RsBWTnw== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICYDCCAckCAQEwDQYJKoZIhvcNAQEFBQAwKDESMBAGA1UEAxMJbWl0bXByb3h5 +MRIwEAYDVQQKEwltaXRtcHJveHkwHhcNMTMwMTIwMDEwODEzWhcNMTUxMDE3MDEw +ODEzWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UE +ChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0 +EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+ +ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G +3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/ +SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP +G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABMA0GCSqGSIb3DQEBBQUA +A4GBAFvI+cd47B85PQ970n2dU/PlA2/Hb1ldrrXh2guR4hX6vYx/uuk5yRI/n0Rd +KOXJ3czO0bd2Fpe3ZoNpkW0pOSDej/Q+58ScuJd0gWCT/Sh1eRk6ZdC0kusOuWoY +bPOPMkG45LPgUMFOnZEsfJP6P5mZIxlbCvSMFC25nPHWlct7 +-----END CERTIFICATE----- diff --git a/test/pathod/data/clientcert/make b/test/pathod/data/clientcert/make new file mode 100644 index 00000000..d1caea81 --- /dev/null +++ b/test/pathod/data/clientcert/make @@ -0,0 +1,8 @@ +#!/bin/sh + +openssl genrsa -out client.key 2048 +openssl req -key client.key -new -out client.req +openssl x509 -req -days 365 -in client.req -signkey client.key -out client.crt -extfile client.cnf -extensions ssl_client +openssl x509 -req -days 1000 -in client.req -CA ~/.mitmproxy/mitmproxy-ca.pem -CAkey ~/.mitmproxy/mitmproxy-ca.pem -set_serial 00001 -out client.crt -extensions ssl_client +cat client.key client.crt > client.pem +openssl x509 -text -noout -in client.pem diff --git a/test/pathod/data/file b/test/pathod/data/file new file mode 100644 index 00000000..26918572 --- /dev/null +++ b/test/pathod/data/file @@ -0,0 +1 @@ +testfile diff --git a/test/pathod/data/request b/test/pathod/data/request new file mode 100644 index 00000000..c4c90e76 --- /dev/null +++ b/test/pathod/data/request @@ -0,0 +1 @@ +get:/foo diff --git a/test/pathod/data/response b/test/pathod/data/response new file mode 100644 index 00000000..8f897c85 --- /dev/null +++ b/test/pathod/data/response @@ -0,0 +1 @@ +202 diff --git a/test/pathod/data/testkey.pem b/test/pathod/data/testkey.pem new file mode 100644 index 00000000..b804bd4c --- /dev/null +++ b/test/pathod/data/testkey.pem @@ -0,0 +1,68 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG5QIBAAKCAYEAwvtKxoZvBV2AxPAkCx8PXbuE7KeqK9bBvk8x+JchPMdf/KZj +sdu2v6Gm8Hi053i7ZGxouFvonJxHAiK6cwk9OYQwa9fbOFf2mgWKEBO4fbCH93tW +DCTdWVxFyNViAvxGHlJs3/IU03pIG29AgUnhRW8pGbabAfx8emcOZJZ3ykEuimaC +4s7mRwdc63GXnbcjTtRkrJsBATI+xvPwuR2+4daX7sPCf0kel3bN2jMpwXfvk/Ww +kJ2BIEeZCg0qIvyMjH9qrUirUnsmQnpPln0CGBbQEBsW9yMfGoFdREiMYls5jZeq +NxjWNv1RTRIm/4RjMwyxnoTA9eDS9wwO2NnJS4vfXAnUTP4BYx8Pe4ZMA2Gm6YrC +ysT6YA1xdHNpcuHXClxwmPj/cm8Z5kIg5clbNIK60ts9yFr/Ao3KPPYJ2GBv8/Oe +ApPBJuubews+/9/13Ew/SJ1t2u28+sPbgXUG8dC2n4vWTvJwKf6Duqxgnm82zdzj +SZoXRQsP984qiN7NAgMBAAECggGBALB6rqWdzCL5DLI0AQun40qdjaR95UKksNvF +5p7we379nl2ZZKb5DSHJ+MWzG1pfJo2wqeAkIBiQQp0mPcgdVrMWeJVD3QHUbDng +RaRjlRr+izJvCeUYANj+8ZLjwECfgf+z7yOLg1oeVeGvAp2C90jXYkYJx6c2lpxb +ZuWYY3hHIw7V1iXfywIDIhFg0TBJMMYK68xmx7QDfFqrNPj4eWsDxqSvvv1iezPw +rkWPBX49RjWPrW5XgSZsZ5J3c+oS1rZmIY7EAgopTWB/3wJjZR1Idz/9l9LIWlBP +6zVC27CIZzSEeGguqNVeyzJ0TPWh5idYNRmSZr6eTUF0245LNO/gqvWKgRSNIZko +HoBa2F1AvCiB67S1kxjwS5y3VkudZE4jkgGKcC2Ws/9QmOZ0HAsjI8VAMp2hj6iN +0HdPMTNtsLgbhKyXsoZuW4YmwfSTPxGi2gvcI7GUozpTz84n1cOneJnz1ygx6Uru +v8DpQg+VX6xTy4X6AK1F8OYNMZ/jaQKBwQDv30NevQStnGbTmcSr+0fd4rmWFklK +V6B2X7zWynVpSGvCWkqVSp3mG6aiZItAltVMRL/9LT6zIheyClyd+vXIjR2+W210 +XMxrvz7A8qCXkvB2dyEhrMdCfZ7p8+kf+eD2c/Mnxb7VpmDfHYLx30JeQoBwjrwU +Eul+dE1P+r8bWBaLTjlsipTya74yItWWAToXAo+s1BXBtXhEsLoe4FghlC0u724d +ucjDaeICdLcerApdvg6Q6p4kVHaoF6ka6I8CgcEA0Bdc05ery9gLC6CclV+BhA5Q +dfDq2P7qhc7e1ipwNRrQo2gy5HhgOkTL3dJWc+8rV6CBP/JfchnsW40tDOnPCTLT +gg3n7vv3RHrtncApXuhIFR+B5xjohTPBzxRUMiAOre2d0F5b6eBXFjptf/1i2tQ+ +qdqJoyOGOZP0hKVslGIfz+CKc6WEkIqX7c91Msdr5myeaWDI5TsurfuKRBH395T3 +BMAi6oinAAEb1rdySenLO2A/0kVmBVlTpaN3TNjjAoHBAMvS4uQ1qSv8okNbfgrF +UqPwa9JkzZImM2tinovFLU9xAl/7aTTCWrmU9Vs4JDuV71kHcjwnngeJCKl4tIpp +HUB06Lk/5xnhYLKNpz087cjeSwXe5IBA2HBfXhFd+NH6+nVwwUUieq4A2n+8C/CK +zVJbH9iE8Lv99fpFyQwU/R63EzD8Hz9j4ny7oLnpb6QvFrVGr98jt/kJwlBb+0sR +RtIBnwMq4F7R5w5lgm6jzpZ5ibVuMeJh+k7Ulp7uu/rpcQKBwQDE3sWIvf7f7PaO +OpbJz0CmYjCHVLWrNIlGrPAv6Jid9U+cuXEkrCpGFl5V77CxIH59+bEuga0BMztl +ZkxP4khoqHhom6VpeWJ3nGGAFJRPYS0JJvTsYalilBPxSYdaoO+iZ6MdxpfozcE2 +m3KLW3uSEqlyYvpCqNJNWQhGEoeGXstADWyPevHPGgAhElwL/ZW8u9inU9Tc4sAI +BGnMer+BsaJ+ERU3lK+Clony+z2aZiFLfIUE93lM6DT2CZBN2QcCgcAVk4L0bfA6 +HFnP/ZWNlnYWpOVFKcq57PX+J5/k7Tf34e2cYM2P0eqYggWZbzVd8qoCOQCHrAx0 +aZSSvEyKAVvzRNeqbm1oXaMojksMnrSX5henHjPbZlr1EmM7+zMnSTMkfVOx/6g1 +97sASej31XdOAgKCBJGymrwvYrCLW+P5cHqd+D8v/PvfpRIQM54p5ixRt3EYZvtR +zGrzsr0OGyOLZtj1DB0a3kvajAAOCl3TawJSzviKo2mwc+/xj28MCQM= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIE4TCCA0mgAwIBAgIJALONCAWZxPhUMA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAk5aMQ4wDAYDVQQIDAVPdGFnbzEPMA0GA1UECgwGUGF0aG9kMREwDwYDVQQD +DAh0ZXN0LmNvbTAeFw0xNTA0MTgyMjA0NTNaFw00MjA5MDIyMjA0NTNaMEExCzAJ +BgNVBAYTAk5aMQ4wDAYDVQQIDAVPdGFnbzEPMA0GA1UECgwGUGF0aG9kMREwDwYD +VQQDDAh0ZXN0LmNvbTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAML7 +SsaGbwVdgMTwJAsfD127hOynqivWwb5PMfiXITzHX/ymY7Hbtr+hpvB4tOd4u2Rs +aLhb6JycRwIiunMJPTmEMGvX2zhX9poFihATuH2wh/d7Vgwk3VlcRcjVYgL8Rh5S +bN/yFNN6SBtvQIFJ4UVvKRm2mwH8fHpnDmSWd8pBLopmguLO5kcHXOtxl523I07U +ZKybAQEyPsbz8LkdvuHWl+7Dwn9JHpd2zdozKcF375P1sJCdgSBHmQoNKiL8jIx/ +aq1Iq1J7JkJ6T5Z9AhgW0BAbFvcjHxqBXURIjGJbOY2XqjcY1jb9UU0SJv+EYzMM +sZ6EwPXg0vcMDtjZyUuL31wJ1Ez+AWMfD3uGTANhpumKwsrE+mANcXRzaXLh1wpc +cJj4/3JvGeZCIOXJWzSCutLbPcha/wKNyjz2Cdhgb/PzngKTwSbrm3sLPv/f9dxM +P0idbdrtvPrD24F1BvHQtp+L1k7ycCn+g7qsYJ5vNs3c40maF0ULD/fOKojezQID +AQABo4HbMIHYMAsGA1UdDwQEAwIFoDAdBgNVHQ4EFgQUbEgfTauEqEP/bnBtby1K +bihJvcswcQYDVR0jBGowaIAUbEgfTauEqEP/bnBtby1KbihJvcuhRaRDMEExCzAJ +BgNVBAYTAk5aMQ4wDAYDVQQIDAVPdGFnbzEPMA0GA1UECgwGUGF0aG9kMREwDwYD +VQQDDAh0ZXN0LmNvbYIJALONCAWZxPhUMAwGA1UdEwQFMAMBAf8wKQYDVR0RBCIw +IIIIdGVzdC5jb22CCXRlc3QyLmNvbYIJdGVzdDMuY29tMA0GCSqGSIb3DQEBCwUA +A4IBgQBcTedXtUb91DxQRtg73iomz7cQ4niZntUBW8iE5rpoA7prtQNGHMCbHwaX +tbWFkzBmL5JTBWvd/6AQ2LtiB3rYB3W/iRhbpsNJ501xaoOguPEQ9720Ph8TEveM +208gNzGsEOcNALwyXj2y9M19NGu9zMa8eu1Tc3IsQaVaGKHx8XZn5HTNUx8EdcwI +Z/Ji9ETDCL7+e5INv0tqfFSazWaQUwxM4IzPMkKTYRcMuN/6eog609k9r9pp32Ut +rKlzc6GIkAlgJJ0Wkoz1V46DmJNJdJG7eLu/mtsB85j6hytIQeWTf1fll5YnMZLF +HgNZtfYn8Q0oTdBQ0ZOaZeQCfZ8emYBdLJf2YB83uGRMjQ1FoeIxzQqiRq8WHRdb +9Q45i0DINMnNp0DbLMA4numZ7wT9SQb6sql9eUyuCNDw7nGIWTHUNfLtU1Er3h1d +icJuApx9+//UN/pGh0yTXb3fZbiI4IehRmkpnIWonIAwaVGm6JZU04wiIn8CuBho +/qQdlS8= +-----END CERTIFICATE----- diff --git a/test/pathod/scripts/generate.sh b/test/pathod/scripts/generate.sh new file mode 100644 index 00000000..eec3077d --- /dev/null +++ b/test/pathod/scripts/generate.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ ! -f ./private.key ] +then + openssl genrsa -out private.key 3072 +fi +openssl req \ + -batch \ + -new -x509 \ + -key private.key \ + -sha256 \ + -out cert.pem \ + -days 9999 \ + -config ./openssl.cnf +openssl x509 -in cert.pem -text -noout +cat ./private.key ./cert.pem > testcert.pem +rm ./private.key ./cert.pem diff --git a/test/pathod/scripts/openssl.cnf b/test/pathod/scripts/openssl.cnf new file mode 100644 index 00000000..5c890354 --- /dev/null +++ b/test/pathod/scripts/openssl.cnf @@ -0,0 +1,39 @@ +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +x509_extensions = v3_ca + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = NZ +countryName_min = 2 +countryName_max = 2 +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Otago +localityName = Locality Name (eg, city) +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Pathod +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = test.com +commonName_max = 64 + +[ v3_req ] + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + +keyUsage = digitalSignature, keyEncipherment +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always +basicConstraints = CA:true +subjectAltName = @alternate_names + + +[ alternate_names ] + +DNS.1 = test.com +DNS.2 = test2.com +DNS.3 = test3.com diff --git a/test/pathod/test_app.py b/test/pathod/test_app.py new file mode 100644 index 00000000..4536db8e --- /dev/null +++ b/test/pathod/test_app.py @@ -0,0 +1,85 @@ +import tutils + + +class TestApp(tutils.DaemonTests): + SSL = False + + def test_index(self): + r = self.getpath("/") + assert r.status_code == 200 + assert r.content + + def test_about(self): + r = self.getpath("/about") + assert r.ok + + def test_download(self): + r = self.getpath("/download") + assert r.ok + + def test_docs(self): + assert self.getpath("/docs/pathod").status_code == 200 + assert self.getpath("/docs/pathoc").status_code == 200 + assert self.getpath("/docs/language").status_code == 200 + assert self.getpath("/docs/libpathod").status_code == 200 + assert self.getpath("/docs/test").status_code == 200 + + def test_log(self): + assert self.getpath("/log").status_code == 200 + assert self.get("200:da").status_code == 200 + id = self.d.log()[0]["id"] + assert self.getpath("/log").status_code == 200 + assert self.getpath("/log/%s" % id).status_code == 200 + assert self.getpath("/log/9999999").status_code == 404 + + def test_log_binary(self): + assert self.get("200:h@10b=@10b:da") + + def test_response_preview(self): + r = self.getpath("/response_preview", params=dict(spec="200")) + assert r.status_code == 200 + assert 'Response' in r.content + + r = self.getpath("/response_preview", params=dict(spec="foo")) + assert r.status_code == 200 + assert 'Error' in r.content + + r = self.getpath("/response_preview", params=dict(spec="200:b@100m")) + assert r.status_code == 200 + assert "too large" in r.content + + r = self.getpath("/response_preview", params=dict(spec="200:b@5k")) + assert r.status_code == 200 + assert 'Response' in r.content + + r = self.getpath( + "/response_preview", + params=dict( + spec="200:b<nonexistent")) + assert r.status_code == 200 + assert 'File access denied' in r.content + + r = self.getpath("/response_preview", params=dict(spec="200:b<file")) + assert r.status_code == 200 + assert 'testfile' in r.content + + def test_request_preview(self): + r = self.getpath("/request_preview", params=dict(spec="get:/")) + assert r.status_code == 200 + assert 'Request' in r.content + + r = self.getpath("/request_preview", params=dict(spec="foo")) + assert r.status_code == 200 + assert 'Error' in r.content + + r = self.getpath("/request_preview", params=dict(spec="get:/:b@100m")) + assert r.status_code == 200 + assert "too large" in r.content + + r = self.getpath("/request_preview", params=dict(spec="get:/:b@5k")) + assert r.status_code == 200 + assert 'Request' in r.content + + r = self.getpath("/request_preview", params=dict(spec="")) + assert r.status_code == 200 + assert 'empty spec' in r.content diff --git a/test/pathod/test_language_actions.py b/test/pathod/test_language_actions.py new file mode 100644 index 00000000..755f0d85 --- /dev/null +++ b/test/pathod/test_language_actions.py @@ -0,0 +1,135 @@ +import cStringIO + +from libpathod.language import actions +from libpathod import language + + +def parse_request(s): + return language.parse_pathoc(s).next() + + +def test_unique_name(): + assert not actions.PauseAt(0, "f").unique_name + assert actions.DisconnectAt(0).unique_name + + +class TestDisconnects: + + def test_parse_pathod(self): + a = language.parse_pathod("400:d0").next().actions[0] + assert a.spec() == "d0" + a = language.parse_pathod("400:dr").next().actions[0] + assert a.spec() == "dr" + + def test_at(self): + e = actions.DisconnectAt.expr() + v = e.parseString("d0")[0] + assert isinstance(v, actions.DisconnectAt) + assert v.offset == 0 + + v = e.parseString("d100")[0] + assert v.offset == 100 + + e = actions.DisconnectAt.expr() + v = e.parseString("dr")[0] + assert v.offset == "r" + + def test_spec(self): + assert actions.DisconnectAt("r").spec() == "dr" + assert actions.DisconnectAt(10).spec() == "d10" + + +class TestInject: + + def test_parse_pathod(self): + a = language.parse_pathod("400:ir,@100").next().actions[0] + assert a.offset == "r" + assert a.value.datatype == "bytes" + assert a.value.usize == 100 + + a = language.parse_pathod("400:ia,@100").next().actions[0] + assert a.offset == "a" + + def test_at(self): + e = actions.InjectAt.expr() + v = e.parseString("i0,'foo'")[0] + assert v.value.val == "foo" + assert v.offset == 0 + assert isinstance(v, actions.InjectAt) + + v = e.parseString("ir,'foo'")[0] + assert v.offset == "r" + + def test_serve(self): + s = cStringIO.StringIO() + r = language.parse_pathod("400:i0,'foo'").next() + assert language.serve(r, s, {}) + + def test_spec(self): + e = actions.InjectAt.expr() + v = e.parseString("i0,'foo'")[0] + assert v.spec() == 'i0,"foo"' + + def test_spec(self): + e = actions.InjectAt.expr() + v = e.parseString("i0,@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +class TestPauses: + + def test_parse_pathod(self): + e = actions.PauseAt.expr() + v = e.parseString("p10,10")[0] + assert v.seconds == 10 + assert v.offset == 10 + + v = e.parseString("p10,f")[0] + assert v.seconds == "f" + + v = e.parseString("pr,f")[0] + assert v.offset == "r" + + v = e.parseString("pa,f")[0] + assert v.offset == "a" + + def test_request(self): + r = language.parse_pathod('400:p10,10').next() + assert r.actions[0].spec() == "p10,10" + + def test_spec(self): + assert actions.PauseAt("r", 5).spec() == "pr,5" + assert actions.PauseAt(0, 5).spec() == "p0,5" + assert actions.PauseAt(0, "f").spec() == "p0,f" + + def test_freeze(self): + l = actions.PauseAt("r", 5) + assert l.freeze({}).spec() == l.spec() + + +class Test_Action: + + def test_cmp(self): + a = actions.DisconnectAt(0) + b = actions.DisconnectAt(1) + c = actions.DisconnectAt(0) + assert a < b + assert a == c + l = sorted([b, a]) + assert l[0].offset == 0 + + def test_resolve(self): + r = parse_request('GET:"/foo"') + e = actions.DisconnectAt("r") + ret = e.resolve({}, r) + assert isinstance(ret.offset, int) + + def test_repr(self): + e = actions.DisconnectAt("r") + assert repr(e) + + def test_freeze(self): + l = actions.DisconnectAt(5) + assert l.freeze({}).spec() == l.spec() diff --git a/test/pathod/test_language_base.py b/test/pathod/test_language_base.py new file mode 100644 index 00000000..b18ee5b2 --- /dev/null +++ b/test/pathod/test_language_base.py @@ -0,0 +1,352 @@ +import os +from libpathod import language +from libpathod.language import base, exceptions +import tutils + + +def parse_request(s): + return language.parse_pathoc(s).next() + + +def test_times(): + reqs = list(language.parse_pathoc("get:/:x5")) + assert len(reqs) == 5 + assert not reqs[0].times + + +def test_caseless_literal(): + class CL(base.CaselessLiteral): + TOK = "foo" + v = CL("foo") + assert v.expr() + assert v.values(language.Settings()) + + +class TestTokValueNakedLiteral: + + def test_expr(self): + v = base.TokValueNakedLiteral("foo") + assert v.expr() + + def test_spec(self): + v = base.TokValueNakedLiteral("foo") + assert v.spec() == repr(v) == "foo" + + v = base.TokValueNakedLiteral("f\x00oo") + assert v.spec() == repr(v) == r"f\x00oo" + + +class TestTokValueLiteral: + + def test_espr(self): + v = base.TokValueLiteral("foo") + assert v.expr() + assert v.val == "foo" + + v = base.TokValueLiteral("foo\n") + assert v.expr() + assert v.val == "foo\n" + assert repr(v) + + def test_spec(self): + v = base.TokValueLiteral("foo") + assert v.spec() == r"'foo'" + + v = base.TokValueLiteral("f\x00oo") + assert v.spec() == repr(v) == r"'f\x00oo'" + + v = base.TokValueLiteral("\"") + assert v.spec() == repr(v) == '\'"\'' + + def roundtrip(self, spec): + e = base.TokValueLiteral.expr() + v = base.TokValueLiteral(spec) + v2 = e.parseString(v.spec()) + assert v.val == v2[0].val + assert v.spec() == v2[0].spec() + + def test_roundtrip(self): + self.roundtrip("'") + self.roundtrip('\'') + self.roundtrip("a") + self.roundtrip("\"") + # self.roundtrip("\\") + self.roundtrip("200:b'foo':i23,'\\''") + self.roundtrip("\a") + + +class TestTokValueGenerate: + + def test_basic(self): + v = base.TokValue.parseString("@10b")[0] + assert v.usize == 10 + assert v.unit == "b" + assert v.bytes() == 10 + v = base.TokValue.parseString("@10")[0] + assert v.unit == "b" + v = base.TokValue.parseString("@10k")[0] + assert v.bytes() == 10240 + v = base.TokValue.parseString("@10g")[0] + assert v.bytes() == 1024 ** 3 * 10 + + v = base.TokValue.parseString("@10g,digits")[0] + assert v.datatype == "digits" + g = v.get_generator({}) + assert g[:100] + + v = base.TokValue.parseString("@10,digits")[0] + assert v.unit == "b" + assert v.datatype == "digits" + + def test_spec(self): + v = base.TokValueGenerate(1, "b", "bytes") + assert v.spec() == repr(v) == "@1" + + v = base.TokValueGenerate(1, "k", "bytes") + assert v.spec() == repr(v) == "@1k" + + v = base.TokValueGenerate(1, "k", "ascii") + assert v.spec() == repr(v) == "@1k,ascii" + + v = base.TokValueGenerate(1, "b", "ascii") + assert v.spec() == repr(v) == "@1,ascii" + + def test_freeze(self): + v = base.TokValueGenerate(100, "b", "ascii") + f = v.freeze(language.Settings()) + assert len(f.val) == 100 + + +class TestTokValueFile: + + def test_file_value(self): + v = base.TokValue.parseString("<'one two'")[0] + assert str(v) + assert v.path == "one two" + + v = base.TokValue.parseString("<path")[0] + assert v.path == "path" + + def test_access_control(self): + v = base.TokValue.parseString("<path")[0] + with tutils.tmpdir() as t: + p = os.path.join(t, "path") + with open(p, "wb") as f: + f.write("x" * 10000) + + assert v.get_generator(language.Settings(staticdir=t)) + + v = base.TokValue.parseString("<path2")[0] + tutils.raises( + exceptions.FileAccessDenied, + v.get_generator, + language.Settings(staticdir=t) + ) + tutils.raises( + "access disabled", + v.get_generator, + language.Settings() + ) + + v = base.TokValue.parseString("</outside")[0] + tutils.raises( + "outside", + v.get_generator, + language.Settings(staticdir=t) + ) + + def test_spec(self): + v = base.TokValue.parseString("<'one two'")[0] + v2 = base.TokValue.parseString(v.spec())[0] + assert v2.path == "one two" + + def test_freeze(self): + v = base.TokValue.parseString("<'one two'")[0] + v2 = v.freeze({}) + assert v2.path == v.path + + +class TestMisc: + + def test_generators(self): + v = base.TokValue.parseString("'val'")[0] + g = v.get_generator({}) + assert g[:] == "val" + + def test_value(self): + assert base.TokValue.parseString("'val'")[0].val == "val" + assert base.TokValue.parseString('"val"')[0].val == "val" + assert base.TokValue.parseString('"\'val\'"')[0].val == "'val'" + + def test_value(self): + class TT(base.Value): + preamble = "m" + e = TT.expr() + v = e.parseString("m'msg'")[0] + assert v.value.val == "msg" + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + v = e.parseString("m@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + def test_fixedlengthvalue(self): + class TT(base.FixedLengthValue): + preamble = "m" + length = 4 + + e = TT.expr() + assert e.parseString("m@4") + tutils.raises("invalid value length", e.parseString, "m@100") + tutils.raises("invalid value length", e.parseString, "m@1") + + with tutils.tmpdir() as t: + p = os.path.join(t, "path") + s = base.Settings(staticdir=t) + with open(p, "wb") as f: + f.write("a" * 20) + v = e.parseString("m<path")[0] + tutils.raises("invalid value length", v.values, s) + + p = os.path.join(t, "path") + with open(p, "wb") as f: + f.write("a" * 4) + v = e.parseString("m<path")[0] + assert v.values(s) + + +class TKeyValue(base.KeyValue): + preamble = "h" + + def values(self, settings): + return [ + self.key.get_generator(settings), + ": ", + self.value.get_generator(settings), + "\r\n", + ] + + +class TestKeyValue: + + def test_simple(self): + e = TKeyValue.expr() + v = e.parseString("h'foo'='bar'")[0] + assert v.key.val == "foo" + assert v.value.val == "bar" + + v2 = e.parseString(v.spec())[0] + assert v2.key.val == v.key.val + assert v2.value.val == v.value.val + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + def test_freeze(self): + e = TKeyValue.expr() + v = e.parseString("h@10=@10'")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.key.val == v3.key.val + assert v2.value.val == v3.value.val + + +def test_intfield(): + class TT(base.IntField): + preamble = "t" + names = { + "one": 1, + "two": 2, + "three": 3 + } + max = 4 + e = TT.expr() + + v = e.parseString("tone")[0] + assert v.value == 1 + assert v.spec() == "tone" + assert v.values(language.Settings()) + + v = e.parseString("t1")[0] + assert v.value == 1 + assert v.spec() == "t1" + + v = e.parseString("t4")[0] + assert v.value == 4 + assert v.spec() == "t4" + + tutils.raises("can't exceed", e.parseString, "t5") + + +def test_options_or_value(): + class TT(base.OptionsOrValue): + options = [ + "one", + "two", + "three" + ] + e = TT.expr() + assert e.parseString("one")[0].value.val == "one" + assert e.parseString("'foo'")[0].value.val == "foo" + assert e.parseString("'get'")[0].value.val == "get" + + assert e.parseString("one")[0].spec() == "one" + assert e.parseString("'foo'")[0].spec() == "'foo'" + + s = e.parseString("one")[0].spec() + assert s == e.parseString(s)[0].spec() + + s = e.parseString("'foo'")[0].spec() + assert s == e.parseString(s)[0].spec() + + v = e.parseString("@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +def test_integer(): + e = base.Integer.expr() + v = e.parseString("200")[0] + assert v.string() == "200" + assert v.spec() == "200" + + assert v.freeze({}).value == v.value + + class BInt(base.Integer): + bounds = (1, 5) + + tutils.raises("must be between", BInt, 0) + tutils.raises("must be between", BInt, 6) + assert BInt(5) + assert BInt(1) + assert BInt(3) + + +class TBoolean(base.Boolean): + name = "test" + + +def test_unique_name(): + b = TBoolean(True) + assert b.unique_name + + +class test_boolean(): + e = TBoolean.expr() + assert e.parseString("test")[0].value + assert not e.parseString("-test")[0].value + + def roundtrip(s): + e = TBoolean.expr() + s2 = e.parseString(s)[0].spec() + v1 = e.parseString(s)[0].value + v2 = e.parseString(s2)[0].value + assert s == s2 + assert v1 == v2 + + roundtrip("test") + roundtrip("-test") diff --git a/test/pathod/test_language_generators.py b/test/pathod/test_language_generators.py new file mode 100644 index 00000000..945560c3 --- /dev/null +++ b/test/pathod/test_language_generators.py @@ -0,0 +1,42 @@ +import os + +from libpathod.language import generators +import tutils + + +def test_randomgenerator(): + g = generators.RandomGenerator("bytes", 100) + assert repr(g) + assert len(g[:10]) == 10 + assert len(g[1:10]) == 9 + assert len(g[:1000]) == 100 + assert len(g[1000:1001]) == 0 + assert g[0] + + +def test_filegenerator(): + with tutils.tmpdir() as t: + path = os.path.join(t, "foo") + f = open(path, "wb") + f.write("x" * 10000) + f.close() + g = generators.FileGenerator(path) + assert len(g) == 10000 + assert g[0] == "x" + assert g[-1] == "x" + assert g[0:5] == "xxxxx" + assert repr(g) + # remove all references to FileGenerator instance to close the file + # handle. + del g + + +def test_transform_generator(): + def trans(offset, data): + return "a" * len(data) + g = "one" + t = generators.TransformGenerator(g, trans) + assert len(t) == len(g) + assert t[0] == "a" + assert t[:] == "a" * len(g) + assert repr(t) diff --git a/test/pathod/test_language_http.py b/test/pathod/test_language_http.py new file mode 100644 index 00000000..26bb6a45 --- /dev/null +++ b/test/pathod/test_language_http.py @@ -0,0 +1,358 @@ +import cStringIO + +from libpathod import language +from libpathod.language import http, base +import tutils + + +def parse_request(s): + return language.parse_pathoc(s).next() + + +def test_make_error_response(): + d = cStringIO.StringIO() + s = http.make_error_response("foo") + language.serve(s, d, {}) + + +class TestRequest: + + def test_nonascii(self): + tutils.raises("ascii", parse_request, "get:\xf0") + + def test_err(self): + tutils.raises(language.ParseException, parse_request, 'GET') + + def test_simple(self): + r = parse_request('GET:"/foo"') + assert r.method.string() == "GET" + assert r.path.string() == "/foo" + r = parse_request('GET:/foo') + assert r.path.string() == "/foo" + r = parse_request('GET:@1k') + assert len(r.path.string()) == 1024 + + def test_multiple(self): + r = list(language.parse_pathoc("GET:/ PUT:/")) + assert r[0].method.string() == "GET" + assert r[1].method.string() == "PUT" + assert len(r) == 2 + + l = """ + GET + "/foo" + ir,@1 + + PUT + + "/foo + + + + bar" + + ir,@1 + """ + r = list(language.parse_pathoc(l)) + assert len(r) == 2 + assert r[0].method.string() == "GET" + assert r[1].method.string() == "PUT" + + l = """ + get:"http://localhost:9999/p/200":ir,@1 + get:"http://localhost:9999/p/200":ir,@2 + """ + r = list(language.parse_pathoc(l)) + assert len(r) == 2 + assert r[0].method.string() == "GET" + assert r[1].method.string() == "GET" + + def test_nested_response(self): + l = "get:/p:s'200'" + r = list(language.parse_pathoc(l)) + assert len(r) == 1 + assert len(r[0].tokens) == 3 + assert isinstance(r[0].tokens[2], http.NestedResponse) + assert r[0].values({}) + + def test_render(self): + s = cStringIO.StringIO() + r = parse_request("GET:'/foo'") + assert language.serve( + r, + s, + language.Settings(request_host="foo.com") + ) + + def test_multiline(self): + l = """ + GET + "/foo" + ir,@1 + """ + r = parse_request(l) + assert r.method.string() == "GET" + assert r.path.string() == "/foo" + assert r.actions + + l = """ + GET + + "/foo + + + + bar" + + ir,@1 + """ + r = parse_request(l) + assert r.method.string() == "GET" + assert r.path.string().endswith("bar") + assert r.actions + + def test_spec(self): + def rt(s): + s = parse_request(s).spec() + assert parse_request(s).spec() == s + rt("get:/foo") + rt("get:/foo:da") + + def test_freeze(self): + r = parse_request("GET:/:b@100").freeze(language.Settings()) + assert len(r.spec()) > 100 + + def test_path_generator(self): + r = parse_request("GET:@100").freeze(language.Settings()) + assert len(r.spec()) > 100 + + def test_websocket(self): + r = parse_request('ws:/path/') + res = r.resolve(language.Settings()) + assert res.method.string().lower() == "get" + assert res.tok(http.Path).value.val == "/path/" + assert res.tok(http.Method).value.val.lower() == "get" + assert http.get_header("Upgrade", res.headers).value.val == "websocket" + + r = parse_request('ws:put:/path/') + res = r.resolve(language.Settings()) + assert r.method.string().lower() == "put" + assert res.tok(http.Path).value.val == "/path/" + assert res.tok(http.Method).value.val.lower() == "put" + assert http.get_header("Upgrade", res.headers).value.val == "websocket" + + +class TestResponse: + + def dummy_response(self): + return language.parse_pathod("400'msg'").next() + + def test_response(self): + r = language.parse_pathod("400:m'msg'").next() + assert r.status_code.string() == "400" + assert r.reason.string() == "msg" + + r = language.parse_pathod("400:m'msg':b@100b").next() + assert r.reason.string() == "msg" + assert r.body.values({}) + assert str(r) + + r = language.parse_pathod("200").next() + assert r.status_code.string() == "200" + assert not r.reason + assert "OK" in [i[:] for i in r.preamble({})] + + def test_render(self): + s = cStringIO.StringIO() + r = language.parse_pathod("400:m'msg'").next() + assert language.serve(r, s, {}) + + r = language.parse_pathod("400:p0,100:dr").next() + assert "p0" in r.spec() + s = r.preview_safe() + assert "p0" not in s.spec() + + def test_raw(self): + s = cStringIO.StringIO() + r = language.parse_pathod("400:b'foo'").next() + language.serve(r, s, {}) + v = s.getvalue() + assert "Content-Length" in v + + s = cStringIO.StringIO() + r = language.parse_pathod("400:b'foo':r").next() + language.serve(r, s, {}) + v = s.getvalue() + assert "Content-Length" not in v + + def test_length(self): + def testlen(x): + s = cStringIO.StringIO() + x = x.next() + language.serve(x, s, language.Settings()) + assert x.length(language.Settings()) == len(s.getvalue()) + testlen(language.parse_pathod("400:m'msg':r")) + testlen(language.parse_pathod("400:m'msg':h'foo'='bar':r")) + testlen(language.parse_pathod("400:m'msg':h'foo'='bar':b@100b:r")) + + def test_maximum_length(self): + def testlen(x): + x = x.next() + s = cStringIO.StringIO() + m = x.maximum_length({}) + language.serve(x, s, {}) + assert m >= len(s.getvalue()) + + r = language.parse_pathod("400:m'msg':b@100:d0") + testlen(r) + + r = language.parse_pathod("400:m'msg':b@100:d0:i0,'foo'") + testlen(r) + + r = language.parse_pathod("400:m'msg':b@100:d0:i0,'foo'") + testlen(r) + + def test_parse_err(self): + tutils.raises( + language.ParseException, language.parse_pathod, "400:msg,b:" + ) + try: + language.parse_pathod("400'msg':b:") + except language.ParseException as v: + assert v.marked() + assert str(v) + + def test_nonascii(self): + tutils.raises("ascii", language.parse_pathod, "foo:b\xf0") + + def test_parse_header(self): + r = language.parse_pathod('400:h"foo"="bar"').next() + assert http.get_header("foo", r.headers) + + def test_parse_pause_before(self): + r = language.parse_pathod("400:p0,10").next() + assert r.actions[0].spec() == "p0,10" + + def test_parse_pause_after(self): + r = language.parse_pathod("400:pa,10").next() + assert r.actions[0].spec() == "pa,10" + + def test_parse_pause_random(self): + r = language.parse_pathod("400:pr,10").next() + assert r.actions[0].spec() == "pr,10" + + def test_parse_stress(self): + # While larger values are known to work on linux, len() technically + # returns an int and a python 2.7 int on windows has 32bit precision. + # Therefore, we should keep the body length < 2147483647 bytes in our + # tests. + r = language.parse_pathod("400:b@1g").next() + assert r.length({}) + + def test_spec(self): + def rt(s): + s = language.parse_pathod(s).next().spec() + assert language.parse_pathod(s).next().spec() == s + rt("400:b@100g") + rt("400") + rt("400:da") + + def test_websockets(self): + r = language.parse_pathod("ws").next() + tutils.raises("no websocket key", r.resolve, language.Settings()) + res = r.resolve(language.Settings(websocket_key="foo")) + assert res.status_code.string() == "101" + + +def test_ctype_shortcut(): + e = http.ShortcutContentType.expr() + v = e.parseString("c'foo'")[0] + assert v.key.val == "Content-Type" + assert v.value.val == "foo" + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + e = http.ShortcutContentType.expr() + v = e.parseString("c@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +def test_location_shortcut(): + e = http.ShortcutLocation.expr() + v = e.parseString("l'foo'")[0] + assert v.key.val == "Location" + assert v.value.val == "foo" + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + e = http.ShortcutLocation.expr() + v = e.parseString("l@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +def test_shortcuts(): + assert language.parse_pathod( + "400:c'foo'").next().headers[0].key.val == "Content-Type" + assert language.parse_pathod( + "400:l'foo'").next().headers[0].key.val == "Location" + + assert "Android" in tutils.render(parse_request("get:/:ua")) + assert "User-Agent" in tutils.render(parse_request("get:/:ua")) + + +def test_user_agent(): + e = http.ShortcutUserAgent.expr() + v = e.parseString("ua")[0] + assert "Android" in v.string() + + e = http.ShortcutUserAgent.expr() + v = e.parseString("u'a'")[0] + assert "Android" not in v.string() + + v = e.parseString("u@100'")[0] + assert len(str(v.freeze({}).value)) > 100 + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +def test_nested_response(): + e = http.NestedResponse.expr() + v = e.parseString("s'200'")[0] + assert v.value.val == "200" + tutils.raises( + language.ParseException, + e.parseString, + "s'foo'" + ) + + v = e.parseString('s"200:b@1"')[0] + assert "@1" in v.spec() + f = v.freeze({}) + assert "@1" not in f.spec() + + +def test_nested_response_freeze(): + e = http.NestedResponse( + base.TokValueLiteral( + "200:b'foo':i10,'\\x27'".encode( + "string_escape" + ) + ) + ) + assert e.freeze({}) + assert e.values({}) + + +def test_unique_components(): + tutils.raises( + "multiple body clauses", + language.parse_pathod, + "400:b@1:b@1" + ) diff --git a/test/pathod/test_language_http2.py b/test/pathod/test_language_http2.py new file mode 100644 index 00000000..9be49452 --- /dev/null +++ b/test/pathod/test_language_http2.py @@ -0,0 +1,233 @@ +import cStringIO + +import netlib +from netlib import tcp +from netlib.http import user_agents + +from libpathod import language +from libpathod.language import http2, base +import tutils + + +def parse_request(s): + return language.parse_pathoc(s, True).next() + + +def parse_response(s): + return language.parse_pathod(s, True).next() + + +def default_settings(): + return language.Settings( + request_host="foo.com", + protocol=netlib.http.http2.HTTP2Protocol(tcp.TCPClient(('localhost', 1234))) + ) + + +def test_make_error_response(): + d = cStringIO.StringIO() + s = http2.make_error_response("foo", "bar") + language.serve(s, d, default_settings()) + + +class TestRequest: + + def test_cached_values(self): + req = parse_request("get:/") + req_id = id(req) + assert req_id == id(req.resolve(default_settings())) + assert req.values(default_settings()) == req.values(default_settings()) + + def test_nonascii(self): + tutils.raises("ascii", parse_request, "get:\xf0") + + def test_err(self): + tutils.raises(language.ParseException, parse_request, 'GET') + + def test_simple(self): + r = parse_request('GET:"/foo"') + assert r.method.string() == "GET" + assert r.path.string() == "/foo" + r = parse_request('GET:/foo') + assert r.path.string() == "/foo" + + def test_multiple(self): + r = list(language.parse_pathoc("GET:/ PUT:/")) + assert r[0].method.string() == "GET" + assert r[1].method.string() == "PUT" + assert len(r) == 2 + + l = """ + GET + "/foo" + + PUT + + "/foo + + + + bar" + """ + r = list(language.parse_pathoc(l, True)) + assert len(r) == 2 + assert r[0].method.string() == "GET" + assert r[1].method.string() == "PUT" + + l = """ + get:"http://localhost:9999/p/200" + get:"http://localhost:9999/p/200" + """ + r = list(language.parse_pathoc(l, True)) + assert len(r) == 2 + assert r[0].method.string() == "GET" + assert r[1].method.string() == "GET" + + def test_render_simple(self): + s = cStringIO.StringIO() + r = parse_request("GET:'/foo'") + assert language.serve( + r, + s, + default_settings(), + ) + + def test_raw_content_length(self): + r = parse_request('GET:/:r') + assert len(r.headers) == 0 + + r = parse_request('GET:/:r:b"foobar"') + assert len(r.headers) == 0 + + r = parse_request('GET:/') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "0") + + r = parse_request('GET:/:b"foobar"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "6") + + r = parse_request('GET:/:b"foobar":h"content-length"="42"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "42") + + r = parse_request('GET:/:r:b"foobar":h"content-length"="42"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "42") + + def test_content_type(self): + r = parse_request('GET:/:r:c"foobar"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-type", "foobar") + + def test_user_agent(self): + r = parse_request('GET:/:r:ua') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("user-agent", user_agents.get_by_shortcut('a')[2]) + + def test_render_with_headers(self): + s = cStringIO.StringIO() + r = parse_request('GET:/foo:h"foo"="bar"') + assert language.serve( + r, + s, + default_settings(), + ) + + def test_nested_response(self): + l = "get:/p/:s'200'" + r = parse_request(l) + assert len(r.tokens) == 3 + assert isinstance(r.tokens[2], http2.NestedResponse) + assert r.values(default_settings()) + + + def test_render_with_body(self): + s = cStringIO.StringIO() + r = parse_request("GET:'/foo':bfoobar") + assert language.serve( + r, + s, + default_settings(), + ) + + def test_spec(self): + def rt(s): + s = parse_request(s).spec() + assert parse_request(s).spec() == s + rt("get:/foo") + + +class TestResponse: + + def test_cached_values(self): + res = parse_response("200") + res_id = id(res) + assert res_id == id(res.resolve(default_settings())) + assert res.values(default_settings()) == res.values(default_settings()) + + def test_nonascii(self): + tutils.raises("ascii", parse_response, "200:\xf0") + + def test_err(self): + tutils.raises(language.ParseException, parse_response, 'GET:/') + + def test_raw_content_length(self): + r = parse_response('200:r') + assert len(r.headers) == 0 + + r = parse_response('200') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "0") + + def test_content_type(self): + r = parse_response('200:r:c"foobar"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-type", "foobar") + + def test_simple(self): + r = parse_response('200:r:h"foo"="bar"') + assert r.status_code.string() == "200" + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("foo", "bar") + assert r.body is None + + r = parse_response('200:r:h"foo"="bar":bfoobar:h"bla"="fasel"') + assert r.status_code.string() == "200" + assert len(r.headers) == 2 + assert r.headers[0].values(default_settings()) == ("foo", "bar") + assert r.headers[1].values(default_settings()) == ("bla", "fasel") + assert r.body.string() == "foobar" + + def test_render_simple(self): + s = cStringIO.StringIO() + r = parse_response('200') + assert language.serve( + r, + s, + default_settings(), + ) + + def test_render_with_headers(self): + s = cStringIO.StringIO() + r = parse_response('200:h"foo"="bar"') + assert language.serve( + r, + s, + default_settings(), + ) + + def test_render_with_body(self): + s = cStringIO.StringIO() + r = parse_response('200:bfoobar') + assert language.serve( + r, + s, + default_settings(), + ) + + def test_spec(self): + def rt(s): + s = parse_response(s).spec() + assert parse_response(s).spec() == s + rt("200:bfoobar") diff --git a/test/pathod/test_language_websocket.py b/test/pathod/test_language_websocket.py new file mode 100644 index 00000000..d98fd33e --- /dev/null +++ b/test/pathod/test_language_websocket.py @@ -0,0 +1,142 @@ + +from libpathod import language +from libpathod.language import websockets +import netlib.websockets +import tutils + + +def parse_request(s): + return language.parse_pathoc(s).next() + + +class TestWebsocketFrame: + + def _test_messages(self, specs, message_klass): + for i in specs: + wf = parse_request(i) + assert isinstance(wf, message_klass) + assert wf + assert wf.values(language.Settings()) + assert wf.resolve(language.Settings()) + + spec = wf.spec() + wf2 = parse_request(spec) + assert wf2.spec() == spec + + def test_server_values(self): + specs = [ + "wf", + "wf:dr", + "wf:b'foo'", + "wf:mask:r'foo'", + "wf:l1024:b'foo'", + "wf:cbinary", + "wf:c1", + "wf:mask:knone", + "wf:fin", + "wf:fin:rsv1:rsv2:rsv3:mask", + "wf:-fin:-rsv1:-rsv2:-rsv3:-mask", + "wf:k@4", + "wf:x10", + ] + self._test_messages(specs, websockets.WebsocketFrame) + + def test_parse_websocket_frames(self): + wf = language.parse_websocket_frame("wf:x10") + assert len(list(wf)) == 10 + tutils.raises( + language.ParseException, + language.parse_websocket_frame, + "wf:x" + ) + + def test_client_values(self): + specs = [ + "wf:f'wf'", + ] + self._test_messages(specs, websockets.WebsocketClientFrame) + + def test_nested_frame(self): + wf = parse_request("wf:f'wf'") + assert wf.nested_frame + + def test_flags(self): + wf = parse_request("wf:fin:mask:rsv1:rsv2:rsv3") + frm = netlib.websockets.Frame.from_bytes(tutils.render(wf)) + assert frm.header.fin + assert frm.header.mask + assert frm.header.rsv1 + assert frm.header.rsv2 + assert frm.header.rsv3 + + wf = parse_request("wf:-fin:-mask:-rsv1:-rsv2:-rsv3") + frm = netlib.websockets.Frame.from_bytes(tutils.render(wf)) + assert not frm.header.fin + assert not frm.header.mask + assert not frm.header.rsv1 + assert not frm.header.rsv2 + assert not frm.header.rsv3 + + def fr(self, spec, **kwargs): + settings = language.base.Settings(**kwargs) + wf = parse_request(spec) + return netlib.websockets.Frame.from_bytes(tutils.render(wf, settings)) + + def test_construction(self): + assert self.fr("wf:c1").header.opcode == 1 + assert self.fr("wf:c0").header.opcode == 0 + assert self.fr("wf:cbinary").header.opcode ==\ + netlib.websockets.OPCODE.BINARY + assert self.fr("wf:ctext").header.opcode ==\ + netlib.websockets.OPCODE.TEXT + + def test_rawbody(self): + frm = self.fr("wf:mask:r'foo'") + assert len(frm.payload) == 3 + assert frm.payload != "foo" + + assert self.fr("wf:r'foo'").payload == "foo" + + def test_construction(self): + # Simple server frame + frm = self.fr("wf:b'foo'") + assert not frm.header.mask + assert not frm.header.masking_key + + # Simple client frame + frm = self.fr("wf:b'foo'", is_client=True) + assert frm.header.mask + assert frm.header.masking_key + frm = self.fr("wf:b'foo':k'abcd'", is_client=True) + assert frm.header.mask + assert frm.header.masking_key == 'abcd' + + # Server frame, mask explicitly set + frm = self.fr("wf:b'foo':mask") + assert frm.header.mask + assert frm.header.masking_key + frm = self.fr("wf:b'foo':k'abcd'") + assert frm.header.mask + assert frm.header.masking_key == 'abcd' + + # Client frame, mask explicitly unset + frm = self.fr("wf:b'foo':-mask", is_client=True) + assert not frm.header.mask + assert not frm.header.masking_key + + frm = self.fr("wf:b'foo':-mask:k'abcd'", is_client=True) + assert not frm.header.mask + # We're reading back a corrupted frame - the first 3 characters of the + # mask is mis-interpreted as the payload + assert frm.payload == "abc" + + def test_knone(self): + with tutils.raises("expected 4 bytes"): + self.fr("wf:b'foo':mask:knone") + + def test_length(self): + assert self.fr("wf:l3:b'foo'").header.payload_length == 3 + frm = self.fr("wf:l2:b'foo'") + assert frm.header.payload_length == 2 + assert frm.payload == "fo" + tutils.raises("expected 1024 bytes", self.fr, "wf:l1024:b'foo'") diff --git a/test/pathod/test_language_writer.py b/test/pathod/test_language_writer.py new file mode 100644 index 00000000..1a532903 --- /dev/null +++ b/test/pathod/test_language_writer.py @@ -0,0 +1,91 @@ +import cStringIO + +from libpathod import language +from libpathod.language import writer + + +def test_send_chunk(): + v = "foobarfoobar" + for bs in range(1, len(v) + 2): + s = cStringIO.StringIO() + writer.send_chunk(s, v, bs, 0, len(v)) + assert s.getvalue() == v + for start in range(len(v)): + for end in range(len(v)): + s = cStringIO.StringIO() + writer.send_chunk(s, v, bs, start, end) + assert s.getvalue() == v[start:end] + + +def test_write_values_inject(): + tst = "foo" + + s = cStringIO.StringIO() + writer.write_values(s, [tst], [(0, "inject", "aaa")], blocksize=5) + assert s.getvalue() == "aaafoo" + + s = cStringIO.StringIO() + writer.write_values(s, [tst], [(1, "inject", "aaa")], blocksize=5) + assert s.getvalue() == "faaaoo" + + s = cStringIO.StringIO() + writer.write_values(s, [tst], [(1, "inject", "aaa")], blocksize=5) + assert s.getvalue() == "faaaoo" + + +def test_write_values_disconnects(): + s = cStringIO.StringIO() + tst = "foo" * 100 + writer.write_values(s, [tst], [(0, "disconnect")], blocksize=5) + assert not s.getvalue() + + +def test_write_values(): + tst = "foobarvoing" + s = cStringIO.StringIO() + writer.write_values(s, [tst], []) + assert s.getvalue() == tst + + for bs in range(1, len(tst) + 2): + for off in range(len(tst)): + s = cStringIO.StringIO() + writer.write_values( + s, [tst], [(off, "disconnect")], blocksize=bs + ) + assert s.getvalue() == tst[:off] + + +def test_write_values_pauses(): + tst = "".join(str(i) for i in range(10)) + for i in range(2, 10): + s = cStringIO.StringIO() + writer.write_values( + s, [tst], [(2, "pause", 0), (1, "pause", 0)], blocksize=i + ) + assert s.getvalue() == tst + + for i in range(2, 10): + s = cStringIO.StringIO() + writer.write_values(s, [tst], [(1, "pause", 0)], blocksize=i) + assert s.getvalue() == tst + + tst = ["".join(str(i) for i in range(10))] * 5 + for i in range(2, 10): + s = cStringIO.StringIO() + writer.write_values(s, tst[:], [(1, "pause", 0)], blocksize=i) + assert s.getvalue() == "".join(tst) + + +def test_write_values_after(): + s = cStringIO.StringIO() + r = language.parse_pathod("400:da").next() + language.serve(r, s, {}) + + s = cStringIO.StringIO() + r = language.parse_pathod("400:pa,0").next() + language.serve(r, s, {}) + + s = cStringIO.StringIO() + r = language.parse_pathod("400:ia,'xx'").next() + language.serve(r, s, {}) + assert s.getvalue().endswith('xx') diff --git a/test/pathod/test_log.py b/test/pathod/test_log.py new file mode 100644 index 00000000..8f38c040 --- /dev/null +++ b/test/pathod/test_log.py @@ -0,0 +1,25 @@ +import StringIO +from libpathod import log +from netlib.exceptions import TcpDisconnect +import netlib.tcp + + +class DummyIO(StringIO.StringIO): + + def start_log(self, *args, **kwargs): + pass + + def get_log(self, *args, **kwargs): + return "" + + +def test_disconnect(): + outf = DummyIO() + rw = DummyIO() + l = log.ConnectionLogger(outf, False, rw, rw) + try: + with l.ctx() as lg: + lg("Test") + except TcpDisconnect: + pass + assert "Test" in outf.getvalue() diff --git a/test/pathod/test_pathoc.py b/test/pathod/test_pathoc.py new file mode 100644 index 00000000..7c912773 --- /dev/null +++ b/test/pathod/test_pathoc.py @@ -0,0 +1,310 @@ +import json +import cStringIO +import re +import OpenSSL +import pytest +from mock import Mock + +from netlib import tcp, http, socks +from netlib.exceptions import HttpException, TcpException, NetlibException +from netlib.http import http1, http2 + +from libpathod import pathoc, test, version, pathod, language +from netlib.tutils import raises +import tutils + + +def test_response(): + r = http.Response("HTTP/1.1", 200, "Message", {}, None, None) + assert repr(r) + + +class _TestDaemon: + ssloptions = pathod.SSLOptions() + + @classmethod + def setup_class(cls): + cls.d = test.Daemon( + ssl=cls.ssl, + ssloptions=cls.ssloptions, + staticdir=tutils.test_data.path("data"), + anchors=[ + (re.compile("/anchor/.*"), "202") + ] + ) + + @classmethod + def teardown_class(cls): + cls.d.shutdown() + + def setUp(self): + self.d.clear_log() + + def test_info(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ssl=self.ssl, + fp=None + ) + c.connect() + resp = c.request("get:/api/info") + assert tuple(json.loads(resp.content)["version"]) == version.IVERSION + + def tval( + self, + requests, + showreq=False, + showresp=False, + explain=False, + showssl=False, + hexdump=False, + timeout=None, + ignorecodes=(), + ignoretimeout=None, + showsummary=True + ): + s = cStringIO.StringIO() + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ssl=self.ssl, + showreq=showreq, + showresp=showresp, + explain=explain, + hexdump=hexdump, + ignorecodes=ignorecodes, + ignoretimeout=ignoretimeout, + showsummary=showsummary, + fp=s + ) + c.connect(showssl=showssl, fp=s) + if timeout: + c.settimeout(timeout) + for i in requests: + r = language.parse_pathoc(i).next() + if explain: + r = r.freeze(language.Settings()) + try: + c.request(r) + except NetlibException: + pass + return s.getvalue() + + +class TestDaemonSSL(_TestDaemon): + ssl = True + ssloptions = pathod.SSLOptions( + request_client_cert=True, + sans=["test1.com", "test2.com"], + alpn_select=b'h2', + ) + + def test_sni(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ssl=True, + sni="foobar.com", + fp=None + ) + c.connect() + c.request("get:/p/200") + r = c.request("get:/api/log") + d = json.loads(r.content) + assert d["log"][0]["request"]["sni"] == "foobar.com" + + def test_showssl(self): + assert "certificate chain" in self.tval(["get:/p/200"], showssl=True) + + def test_clientcert(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ssl=True, + clientcert=tutils.test_data.path("data/clientcert/client.pem"), + fp=None + ) + c.connect() + c.request("get:/p/200") + r = c.request("get:/api/log") + d = json.loads(r.content) + assert d["log"][0]["request"]["clientcert"]["keyinfo"] + + def test_http2_without_ssl(self): + fp = cStringIO.StringIO() + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + use_http2=True, + ssl=False, + fp = fp + ) + tutils.raises(NotImplementedError, c.connect) + + +class TestDaemon(_TestDaemon): + ssl = False + + def test_ssl_error(self): + c = pathoc.Pathoc(("127.0.0.1", self.d.port), ssl=True, fp=None) + tutils.raises("ssl handshake", c.connect) + + def test_showssl(self): + assert not "certificate chain" in self.tval( + ["get:/p/200"], + showssl=True) + + def test_ignorecodes(self): + assert "200" in self.tval(["get:'/p/200:b@1'"]) + assert "200" in self.tval(["get:'/p/200:b@1'"]) + assert "200" in self.tval(["get:'/p/200:b@1'"]) + assert "200" not in self.tval(["get:'/p/200:b@1'"], ignorecodes=[200]) + assert "200" not in self.tval( + ["get:'/p/200:b@1'"], + ignorecodes=[ + 200, + 201]) + assert "202" in self.tval(["get:'/p/202:b@1'"], ignorecodes=[200, 201]) + + def test_timeout(self): + assert "Timeout" in self.tval(["get:'/p/200:p0,100'"], timeout=0.01) + assert "HTTP" in self.tval( + ["get:'/p/200:p5,100'"], + showresp=True, + timeout=1 + ) + assert not "HTTP" in self.tval( + ["get:'/p/200:p3,100'"], + showresp=True, + timeout=1, + ignoretimeout=True + ) + + def test_showresp(self): + reqs = ["get:/api/info:p0,0", "get:/api/info:p0,0"] + assert self.tval(reqs).count("200") == 2 + assert self.tval(reqs, showresp=True).count("HTTP/1.1 200 OK") == 2 + assert self.tval( + reqs, showresp=True, hexdump=True + ).count("0000000000") == 2 + + def test_showresp_httperr(self): + v = self.tval(["get:'/p/200:d20'"], showresp=True, showsummary=True) + assert "Invalid headers" in v + assert "HTTP/" in v + + def test_explain(self): + reqs = ["get:/p/200:b@100"] + assert "b@100" not in self.tval(reqs, explain=True) + + def test_showreq(self): + reqs = ["get:/api/info:p0,0", "get:/api/info:p0,0"] + assert self.tval(reqs, showreq=True).count("GET /api") == 2 + assert self.tval( + reqs, showreq=True, hexdump=True + ).count("0000000000") == 2 + + def test_conn_err(self): + assert "Invalid server response" in self.tval(["get:'/p/200:d2'"]) + + def test_websocket_shutdown(self): + c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) + c.connect() + c.request("ws:/") + c.stop() + + @pytest.mark.xfail + def test_wait_finish(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + fp=None, + ws_read_limit=1 + ) + c.connect() + c.request("ws:/") + c.request("wf:f'wf:x100'") + [i for i in c.wait(timeout=0, finish=False)] + [i for i in c.wait(timeout=0)] + + def test_connect_fail(self): + to = ("foobar", 80) + c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) + c.rfile, c.wfile = cStringIO.StringIO(), cStringIO.StringIO() + with raises("connect failed"): + c.http_connect(to) + c.rfile = cStringIO.StringIO( + "HTTP/1.1 500 OK\r\n" + ) + with raises("connect failed"): + c.http_connect(to) + c.rfile = cStringIO.StringIO( + "HTTP/1.1 200 OK\r\n" + ) + c.http_connect(to) + + def test_socks_connect(self): + to = ("foobar", 80) + c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) + c.rfile, c.wfile = tutils.treader(""), cStringIO.StringIO() + tutils.raises(pathoc.PathocError, c.socks_connect, to) + + c.rfile = tutils.treader( + "\x05\xEE" + ) + tutils.raises("SOCKS without authentication", c.socks_connect, ("example.com", 0xDEAD)) + + c.rfile = tutils.treader( + "\x05\x00" + + "\x05\xEE\x00\x03\x0bexample.com\xDE\xAD" + ) + tutils.raises("SOCKS server error", c.socks_connect, ("example.com", 0xDEAD)) + + c.rfile = tutils.treader( + "\x05\x00" + + "\x05\x00\x00\x03\x0bexample.com\xDE\xAD" + ) + c.socks_connect(("example.com", 0xDEAD)) + + +class TestDaemonHTTP2(_TestDaemon): + ssl = True + + if tcp.HAS_ALPN: + + def test_http2(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + fp=None, + ssl=True, + use_http2=True, + ) + assert isinstance(c.protocol, http2.HTTP2Protocol) + + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ) + assert c.protocol == http1 + + def test_http2_alpn(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + fp=None, + ssl=True, + use_http2=True, + http2_skip_connection_preface=True, + ) + + tmp_convert_to_ssl = c.convert_to_ssl + c.convert_to_ssl = Mock() + c.convert_to_ssl.side_effect = tmp_convert_to_ssl + c.connect() + + _, kwargs = c.convert_to_ssl.call_args + assert set(kwargs['alpn_protos']) == set([b'http/1.1', b'h2']) + + def test_request(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + fp=None, + ssl=True, + use_http2=True, + ) + c.connect() + resp = c.request("get:/p/200") + assert resp.status_code == 200 diff --git a/test/pathod/test_pathoc_cmdline.py b/test/pathod/test_pathoc_cmdline.py new file mode 100644 index 00000000..74dfef57 --- /dev/null +++ b/test/pathod/test_pathoc_cmdline.py @@ -0,0 +1,59 @@ +from libpathod import pathoc_cmdline as cmdline +import tutils +import cStringIO +import mock + + +@mock.patch("argparse.ArgumentParser.error") +def test_pathoc(perror): + assert cmdline.args_pathoc(["pathoc", "foo.com", "get:/"]) + s = cStringIO.StringIO() + with tutils.raises(SystemExit): + cmdline.args_pathoc(["pathoc", "--show-uas"], s, s) + + a = cmdline.args_pathoc(["pathoc", "foo.com:8888", "get:/"]) + assert a.port == 8888 + + a = cmdline.args_pathoc(["pathoc", "foo.com:xxx", "get:/"]) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathoc(["pathoc", "-I", "10, 20", "foo.com:8888", "get:/"]) + assert a.ignorecodes == [10, 20] + + a = cmdline.args_pathoc(["pathoc", "-I", "xx, 20", "foo.com:8888", "get:/"]) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathoc(["pathoc", "-c", "foo:10", "foo.com:8888", "get:/"]) + assert a.connect_to == ["foo", 10] + + a = cmdline.args_pathoc(["pathoc", "foo.com", "get:/", "--http2"]) + assert a.use_http2 == True + assert a.ssl == True + + a = cmdline.args_pathoc(["pathoc", "foo.com", "get:/", "--http2-skip-connection-preface"]) + assert a.use_http2 == True + assert a.ssl == True + assert a.http2_skip_connection_preface == True + + a = cmdline.args_pathoc(["pathoc", "-c", "foo", "foo.com:8888", "get:/"]) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathoc( + ["pathoc", "-c", "foo:bar", "foo.com:8888", "get:/"]) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathoc( + [ + "pathoc", + "foo.com:8888", + tutils.test_data.path("data/request") + ] + ) + assert len(list(a.requests)) == 1 + + with tutils.raises(SystemExit): + cmdline.args_pathoc(["pathoc", "foo.com", "invalid"], s, s) diff --git a/test/pathod/test_pathod.py b/test/pathod/test_pathod.py new file mode 100644 index 00000000..ee5fc7bd --- /dev/null +++ b/test/pathod/test_pathod.py @@ -0,0 +1,289 @@ +import sys +import cStringIO +import OpenSSL + +from libpathod import pathod, version +from netlib import tcp, http +from netlib.exceptions import HttpException, TlsException +import tutils + + +class TestPathod(object): + + def test_logging(self): + s = cStringIO.StringIO() + p = pathod.Pathod(("127.0.0.1", 0), logfp=s) + assert len(p.get_log()) == 0 + id = p.add_log(dict(s="foo")) + assert p.log_by_id(id) + assert len(p.get_log()) == 1 + p.clear_log() + assert len(p.get_log()) == 0 + + for _ in range(p.LOGBUF + 1): + p.add_log(dict(s="foo")) + assert len(p.get_log()) <= p.LOGBUF + + +class TestNoWeb(tutils.DaemonTests): + noweb = True + + def test_noweb(self): + assert self.get("200:da").status_code == 200 + assert self.getpath("/").status_code == 800 + + +class TestTimeout(tutils.DaemonTests): + timeout = 0.01 + + def test_noweb(self): + # FIXME: Add float values to spec language, reduce test timeout to + # increase test performance + # This is a bodge - we have some platform difference that causes + # different exceptions to be raised here. + tutils.raises(Exception, self.pathoc, ["get:/:p1,1"]) + assert self.d.last_log()["type"] == "timeout" + + +class TestNoApi(tutils.DaemonTests): + noapi = True + + def test_noapi(self): + assert self.getpath("/log").status_code == 404 + r = self.getpath("/") + assert r.status_code == 200 + assert not "Log" in r.content + + +class TestNotAfterConnect(tutils.DaemonTests): + ssl = False + ssloptions = dict( + not_after_connect=True + ) + + def test_connect(self): + r, _ = self.pathoc( + [r"get:'http://foo.com/p/202':da"], + connect_to=("localhost", self.d.port) + ) + assert r[0].status_code == 202 + + +class TestCustomCert(tutils.DaemonTests): + ssl = True + ssloptions = dict( + certs=[("*", tutils.test_data.path("data/testkey.pem"))], + ) + + def test_connect(self): + r, _ = self.pathoc([r"get:/p/202"]) + r = r[0] + assert r.status_code == 202 + assert r.sslinfo + assert "test.com" in str(r.sslinfo.certchain[0].get_subject()) + + +class TestSSLCN(tutils.DaemonTests): + ssl = True + ssloptions = dict( + cn="foo.com" + ) + + def test_connect(self): + r, _ = self.pathoc([r"get:/p/202"]) + r = r[0] + assert r.status_code == 202 + assert r.sslinfo + assert r.sslinfo.certchain[0].get_subject().CN == "foo.com" + + +class TestNohang(tutils.DaemonTests): + nohang = True + + def test_nohang(self): + r = self.get("200:p0,0") + assert r.status_code == 800 + l = self.d.last_log() + assert "Pauses have been disabled" in l["response"]["msg"] + + +class TestHexdump(tutils.DaemonTests): + hexdump = True + + def test_hexdump(self): + r = self.get(r"200:b'\xf0'") + + +class TestNocraft(tutils.DaemonTests): + nocraft = True + + def test_nocraft(self): + r = self.get(r"200:b'\xf0'") + assert r.status_code == 800 + assert "Crafting disabled" in r.content + + +class CommonTests(tutils.DaemonTests): + + def test_binarydata(self): + r = self.get(r"200:b'\xf0'") + l = self.d.last_log() + # FIXME: Other binary data elements + + def test_sizelimit(self): + r = self.get("200:b@1g") + assert r.status_code == 800 + l = self.d.last_log() + assert "too large" in l["response"]["msg"] + + def test_preline(self): + r, _ = self.pathoc([r"get:'/p/200':i0,'\r\n'"]) + assert r[0].status_code == 200 + + def test_info(self): + assert tuple(self.d.info()["version"]) == version.IVERSION + + def test_logs(self): + assert self.d.clear_log() + assert not self.d.last_log() + rsp = self.get("202:da") + assert len(self.d.log()) == 1 + assert self.d.clear_log() + assert len(self.d.log()) == 0 + + def test_disconnect(self): + rsp = self.get("202:b@100k:d200") + assert len(rsp.content) < 200 + + def test_parserr(self): + rsp = self.get("400:msg,b:") + assert rsp.status_code == 800 + + def test_static(self): + rsp = self.get("200:b<file") + assert rsp.status_code == 200 + assert rsp.content.strip() == "testfile" + + def test_anchor(self): + rsp = self.getpath("anchor/foo") + assert rsp.status_code == 202 + + def test_invalid_first_line(self): + c = tcp.TCPClient(("localhost", self.d.port)) + c.connect() + if self.ssl: + c.convert_to_ssl() + c.wfile.write("foo\n\n\n") + c.wfile.flush() + l = self.d.last_log() + assert l["type"] == "error" + assert "foo" in l["msg"] + + def test_invalid_content_length(self): + tutils.raises( + HttpException, + self.pathoc, + ["get:/:h'content-length'='foo'"] + ) + l = self.d.last_log() + assert l["type"] == "error" + assert "Unparseable Content Length" in l["msg"] + + def test_invalid_headers(self): + tutils.raises(HttpException, self.pathoc, ["get:/:h'\t'='foo'"]) + l = self.d.last_log() + assert l["type"] == "error" + assert "Invalid headers" in l["msg"] + + def test_access_denied(self): + rsp = self.get("=nonexistent") + assert rsp.status_code == 800 + + def test_source_access_denied(self): + rsp = self.get("200:b</foo") + assert rsp.status_code == 800 + assert "File access denied" in rsp.content + + def test_proxy(self): + r, _ = self.pathoc([r"get:'http://foo.com/p/202':da"]) + assert r[0].status_code == 202 + + def test_websocket(self): + r, _ = self.pathoc(["ws:/p/"], ws_read_limit=0) + assert r[0].status_code == 101 + + r, _ = self.pathoc(["ws:/p/ws"], ws_read_limit=0) + assert r[0].status_code == 101 + + def test_websocket_frame(self): + r, _ = self.pathoc( + ["ws:/p/", "wf:f'wf:b\"test\"':pa,1"], + ws_read_limit=1 + ) + assert r[1].payload == "test" + + def test_websocket_frame_reflect_error(self): + r, _ = self.pathoc( + ["ws:/p/", "wf:-mask:knone:f'wf:b@10':i13,'a'"], + ws_read_limit=1, + timeout=1 + ) + # FIXME: Race Condition? + assert "Parse error" in self.d.text_log() + + def test_websocket_frame_disconnect_error(self): + self.pathoc(["ws:/p/", "wf:b@10:d3"], ws_read_limit=0) + assert self.d.last_log() + + +class TestDaemon(CommonTests): + ssl = False + + def test_connect(self): + r, _ = self.pathoc( + [r"get:'http://foo.com/p/202':da"], + connect_to=("localhost", self.d.port), + ssl=True + ) + assert r[0].status_code == 202 + + def test_connect_err(self): + tutils.raises( + HttpException, + self.pathoc, + [r"get:'http://foo.com/p/202':da"], + connect_to=("localhost", self.d.port) + ) + + +class TestDaemonSSL(CommonTests): + ssl = True + + def test_ssl_conn_failure(self): + c = tcp.TCPClient(("localhost", self.d.port)) + c.rbufsize = 0 + c.wbufsize = 0 + c.connect() + c.wfile.write("\0\0\0\0") + tutils.raises(TlsException, c.convert_to_ssl) + l = self.d.last_log() + assert l["type"] == "error" + assert "SSL" in l["msg"] + + def test_ssl_cipher(self): + r, _ = self.pathoc([r"get:/p/202"]) + assert r[0].status_code == 202 + assert self.d.last_log()["cipher"][1] > 0 + + +class TestHTTP2(tutils.DaemonTests): + ssl = True + noweb = True + noapi = True + nohang = True + + if tcp.HAS_ALPN: + + def test_http2(self): + r, _ = self.pathoc(["GET:/"], ssl=True, use_http2=True) + assert r[0].status_code == 800 diff --git a/test/pathod/test_pathod_cmdline.py b/test/pathod/test_pathod_cmdline.py new file mode 100644 index 00000000..829c4b32 --- /dev/null +++ b/test/pathod/test_pathod_cmdline.py @@ -0,0 +1,85 @@ +from libpathod import pathod_cmdline as cmdline +import tutils +import cStringIO +import mock + + +@mock.patch("argparse.ArgumentParser.error") +def test_pathod(perror): + assert cmdline.args_pathod(["pathod"]) + + a = cmdline.args_pathod( + [ + "pathod", + "--cert", + tutils.test_data.path("data/testkey.pem") + ] + ) + assert a.ssl_certs + + a = cmdline.args_pathod( + [ + "pathod", + "--cert", + "nonexistent" + ] + ) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathod( + [ + "pathod", + "-a", + "foo=200" + ] + ) + assert a.anchors + + a = cmdline.args_pathod( + [ + "pathod", + "-a", + "foo=" + tutils.test_data.path("data/response") + ] + ) + assert a.anchors + + a = cmdline.args_pathod( + [ + "pathod", + "-a", + "?=200" + ] + ) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathod( + [ + "pathod", + "-a", + "foo" + ] + ) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathod( + [ + "pathod", + "--limit-size", + "200k" + ] + ) + assert a.sizelimit + + a = cmdline.args_pathod( + [ + "pathod", + "--limit-size", + "q" + ] + ) + assert perror.called + perror.reset_mock() diff --git a/test/pathod/test_test.py b/test/pathod/test_test.py new file mode 100644 index 00000000..bd92d864 --- /dev/null +++ b/test/pathod/test_test.py @@ -0,0 +1,45 @@ +import logging +import requests +from libpathod import test +import tutils +logging.disable(logging.CRITICAL) + + +class TestDaemonManual: + + def test_simple(self): + with test.Daemon() as d: + rsp = requests.get("http://localhost:%s/p/202:da" % d.port) + assert rsp.ok + assert rsp.status_code == 202 + with tutils.raises(requests.ConnectionError): + requests.get("http://localhost:%s/p/202:da" % d.port) + + def test_startstop_ssl(self): + d = test.Daemon(ssl=True) + rsp = requests.get( + "https://localhost:%s/p/202:da" % + d.port, + verify=False) + assert rsp.ok + assert rsp.status_code == 202 + d.shutdown() + with tutils.raises(requests.ConnectionError): + requests.get("http://localhost:%s/p/202:da" % d.port) + + def test_startstop_ssl_explicit(self): + ssloptions = dict( + certfile=tutils.test_data.path("data/testkey.pem"), + cacert=tutils.test_data.path("data/testkey.pem"), + ssl_after_connect=False + ) + d = test.Daemon(ssl=ssloptions) + rsp = requests.get( + "https://localhost:%s/p/202:da" % + d.port, + verify=False) + assert rsp.ok + assert rsp.status_code == 202 + d.shutdown() + with tutils.raises(requests.ConnectionError): + requests.get("http://localhost:%s/p/202:da" % d.port) diff --git a/test/pathod/test_utils.py b/test/pathod/test_utils.py new file mode 100644 index 00000000..7d24e9e4 --- /dev/null +++ b/test/pathod/test_utils.py @@ -0,0 +1,39 @@ +from libpathod import utils +import tutils + + +def test_membool(): + m = utils.MemBool() + assert not m.v + assert m(1) + assert m.v == 1 + assert m(2) + assert m.v == 2 + + +def test_parse_size(): + assert utils.parse_size("100") == 100 + assert utils.parse_size("100k") == 100 * 1024 + tutils.raises("invalid size spec", utils.parse_size, "foo") + tutils.raises("invalid size spec", utils.parse_size, "100kk") + + +def test_parse_anchor_spec(): + assert utils.parse_anchor_spec("foo=200") == ("foo", "200") + assert utils.parse_anchor_spec("foo") is None + + +def test_data_path(): + tutils.raises(ValueError, utils.data.path, "nonexistent") + + +def test_inner_repr(): + assert utils.inner_repr("\x66") == "\x66" + assert utils.inner_repr(u"foo") == "foo" + + +def test_escape_unprintables(): + s = "".join([chr(i) for i in range(255)]) + e = utils.escape_unprintables(s) + assert e.encode('ascii') + assert not "PATHOD_MARKER" in e diff --git a/test/pathod/tutils.py b/test/pathod/tutils.py new file mode 100644 index 00000000..664cdd52 --- /dev/null +++ b/test/pathod/tutils.py @@ -0,0 +1,128 @@ +import tempfile +import os +import re +import shutil +import cStringIO +from contextlib import contextmanager + +import netlib +from libpathod import utils, test, pathoc, pathod, language +from netlib import tcp +import requests + +def treader(bytes): + """ + Construct a tcp.Read object from bytes. + """ + fp = cStringIO.StringIO(bytes) + return tcp.Reader(fp) + + +class DaemonTests(object): + noweb = False + noapi = False + nohang = False + ssl = False + timeout = None + hexdump = False + ssloptions = None + nocraft = False + + @classmethod + def setup_class(cls): + opts = cls.ssloptions or {} + cls.confdir = tempfile.mkdtemp() + opts["confdir"] = cls.confdir + so = pathod.SSLOptions(**opts) + cls.d = test.Daemon( + staticdir=test_data.path("data"), + anchors=[ + (re.compile("/anchor/.*"), "202:da") + ], + ssl=cls.ssl, + ssloptions=so, + sizelimit=1 * 1024 * 1024, + noweb=cls.noweb, + noapi=cls.noapi, + nohang=cls.nohang, + timeout=cls.timeout, + hexdump=cls.hexdump, + nocraft=cls.nocraft, + logreq=True, + logresp=True, + explain=True + ) + + @classmethod + def teardown_class(cls): + cls.d.shutdown() + shutil.rmtree(cls.confdir) + + def teardown(self): + if not (self.noweb or self.noapi): + self.d.clear_log() + + def getpath(self, path, params=None): + scheme = "https" if self.ssl else "http" + resp = requests.get( + "%s://localhost:%s/%s" % ( + scheme, + self.d.port, + path + ), + verify=False, + params=params + ) + return resp + + def get(self, spec): + resp = requests.get(self.d.p(spec), verify=False) + return resp + + def pathoc( + self, + specs, + timeout=None, + connect_to=None, + ssl=None, + ws_read_limit=None, + use_http2=False, + ): + """ + Returns a (messages, text log) tuple. + """ + if ssl is None: + ssl = self.ssl + logfp = cStringIO.StringIO() + c = pathoc.Pathoc( + ("localhost", self.d.port), + ssl=ssl, + ws_read_limit=ws_read_limit, + timeout=timeout, + fp=logfp, + use_http2=use_http2, + ) + c.connect(connect_to) + ret = [] + for i in specs: + resp = c.request(i) + if resp: + ret.append(resp) + for frm in c.wait(): + ret.append(frm) + c.stop() + return ret, logfp.getvalue() + + +tmpdir = netlib.tutils.tmpdir + +raises = netlib.tutils.raises + +test_data = utils.Data(__name__) + + +def render(r, settings=language.Settings()): + r = r.resolve(settings) + s = cStringIO.StringIO() + assert language.serve(r, s, settings) + return s.getvalue() |