diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8429c3a0..0618c193 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ This project adheres to `Semantic Versioning `_. `2.0.0-dev`_ (unreleased) ------------------------- * Removed Python 2.7 support (`EOL Jan 2020 `_). +* Removed Python’s default limit of 100 response headers. +* Added ``--max-headers`` to allow setting the max header limit. + `1.0.3`_ (2019-08-26) ------------------------- diff --git a/README.rst b/README.rst index 5f7a6da3..91bfc343 100644 --- a/README.rst +++ b/README.rst @@ -646,6 +646,19 @@ To send a header with an empty value, use ``Header;``: $ http httpbin.org/headers 'Header;' +Limiting response headers +------------------------- + +The ``--max-headers=n`` options allows you to control the number of headers +HTTPie tries reads before giving up (the default 0, i.e., there’s no limit). + + +.. code-block:: bash + + $ http --max-headers=100 httpbin.org/get + + + Cookies ======= diff --git a/httpie/cli.py b/httpie/cli.py index ac2a8dd7..96a701fe 100644 --- a/httpie/cli.py +++ b/httpie/cli.py @@ -512,6 +512,17 @@ network.add_argument( """ ) +network.add_argument( + '--max-headers', + type=int, + default=0, + help=""" + The maximum number of response headers to be read before giving up + (default 0, i.e., no limit). + + """ +) + network.add_argument( '--timeout', type=float, diff --git a/httpie/client.py b/httpie/client.py index 91677ea8..e8a8d45c 100644 --- a/httpie/client.py +++ b/httpie/client.py @@ -1,7 +1,9 @@ import json import sys +import http.client import requests +from decorator import contextmanager from requests.adapters import HTTPAdapter from requests.structures import CaseInsensitiveDict @@ -30,6 +32,18 @@ JSON_ACCEPT = '{0}, */*'.format(JSON_CONTENT_TYPE) DEFAULT_UA = 'HTTPie/%s' % __version__ +# noinspection PyProtectedMember +@contextmanager +def max_headers(limit): + # + orig = http.client._MAXHEADERS + http.client._MAXHEADERS = limit or float('Inf') + try: + yield + finally: + http.client._MAXHEADERS = orig + + class HTTPieHTTPAdapter(HTTPAdapter): def __init__(self, ssl_version=None, **kwargs): @@ -64,19 +78,20 @@ def get_response(args, config_dir): requests_session = get_requests_session(ssl_version) requests_session.max_redirects = args.max_redirects - if not args.session and not args.session_read_only: - kwargs = get_requests_kwargs(args) - if args.debug: - dump_request(kwargs) - response = requests_session.request(**kwargs) - else: - response = sessions.get_response( - requests_session=requests_session, - args=args, - config_dir=config_dir, - session_name=args.session or args.session_read_only, - read_only=bool(args.session_read_only), - ) + with max_headers(args.max_headers): + if not args.session and not args.session_read_only: + kwargs = get_requests_kwargs(args) + if args.debug: + dump_request(kwargs) + response = requests_session.request(**kwargs) + else: + response = sessions.get_response( + requests_session=requests_session, + args=args, + config_dir=config_dir, + session_name=args.session or args.session_read_only, + read_only=bool(args.session_read_only), + ) return response diff --git a/tests/test_errors.py b/tests/test_errors.py index 0274e011..c56e773b 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -5,6 +5,8 @@ from requests.exceptions import ConnectionError from httpie import ExitStatus from httpie.core import main +from utils import http, HTTP_OK + error_msg = None @@ -47,3 +49,13 @@ def test_timeout(get_response): ret = main(['--ignore-stdin', 'www.google.com'], custom_log_error=error) assert ret == ExitStatus.ERROR_TIMEOUT assert error_msg == 'Request timed out (30s).' + + +def test_max_headers_limit(httpbin_both): + with raises(ConnectionError) as e: + http('--max-headers=1', httpbin_both + '/get') + assert 'got more than 1 headers' in str(e.value) + + +def test_max_headers_no_limit(httpbin_both): + assert HTTP_OK in http('--max-headers=0', httpbin_both + '/get')