From c5ca9d248eaad2dc04f2388a784a62d2e2bcbc84 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Wed, 17 Jul 2019 22:58:37 -0600 Subject: [PATCH 1/2] Allow stdin to be a closed fd Before this change, the following invocation would not work ``` $ http http://neverhttps.com <&- ``` The "<&-" at the end closes the stdin fd. Specifically, it would fail with ``` ... File "/home/mgsloan/.local/lib/python3.6/site-packages/httpie/context.py", line 26, in Environment stdin_isatty = stdin.isatty() AttributeError: 'NoneType' object has no attribute 'isatty' ``` This can occur when httpie is being programmatically invoked, and may as well be supported. --- httpie/context.py | 4 ++-- httpie/input.py | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/httpie/context.py b/httpie/context.py index bc555863..3873024d 100644 --- a/httpie/context.py +++ b/httpie/context.py @@ -23,7 +23,7 @@ class Environment(object): is_windows = is_windows config_dir = DEFAULT_CONFIG_DIR stdin = sys.stdin - stdin_isatty = stdin.isatty() + stdin_isatty = stdin.isatty() if stdin else False stdin_encoding = None stdout = sys.stdout stdout_isatty = stdout.isatty() @@ -61,7 +61,7 @@ class Environment(object): self.__dict__.update(**kwargs) # Keyword arguments > stream.encoding > default utf8 - if self.stdin_encoding is None: + if self.stdin and self.stdin_encoding is None: self.stdin_encoding = getattr( self.stdin, 'encoding', None) or 'utf8' if self.stdout_encoding is None: diff --git a/httpie/input.py b/httpie/input.py index 1ab34c9b..2e40d82d 100644 --- a/httpie/input.py +++ b/httpie/input.py @@ -142,6 +142,12 @@ class HTTPieArgumentParser(ArgumentParser): if self.args.debug: self.args.traceback = True + self.has_stdin_data = ( + self.env.stdin + and not self.args.ignore_stdin + and not self.env.stdin_isatty + ) + # Arguments processing and environment setup. self._apply_no_options(no_options) self._validate_download_options() @@ -150,7 +156,8 @@ class HTTPieArgumentParser(ArgumentParser): self._process_pretty_options() self._guess_method() self._parse_items() - if not self.args.ignore_stdin and not env.stdin_isatty: + + if self.has_stdin_data: self._body_from_file(self.env.stdin) if not URL_SCHEME_RE.match(self.args.url): scheme = self.args.default_scheme + "://" @@ -315,7 +322,7 @@ class HTTPieArgumentParser(ArgumentParser): if self.args.method is None: # Invoked as `http URL'. assert not self.args.items - if not self.args.ignore_stdin and not self.env.stdin_isatty: + if self.has_stdin_data: self.args.method = HTTP_POST else: self.args.method = HTTP_GET @@ -339,7 +346,7 @@ class HTTPieArgumentParser(ArgumentParser): self.args.url = self.args.method # Infer the method has_data = ( - (not self.args.ignore_stdin and not self.env.stdin_isatty) + self.has_stdin_data or any( item.sep in SEP_GROUP_DATA_ITEMS for item in self.args.items From ced9212c1f0e537bd3baf0471547f5109e87ed71 Mon Sep 17 00:00:00 2001 From: Jakub Roztocil Date: Thu, 29 Aug 2019 13:39:42 +0200 Subject: [PATCH 2/2] Allow stdin to be a closed fd #791 --- .travis.yml | 1 - CHANGELOG.rst | 1 + README.rst | 2 +- httpie/context.py | 5 ++++- httpie/input.py | 1 + tests/test_cli.py | 6 +++++- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3457e405..ae55a672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ env: global: - NEWEST_PYTHON=3.7 python: - - 3.5 - 3.6 # - 3.7 # is done in the matrix below as described in travis-ci/travis-ci#9069 # pypy3 currently fails because of a Flask issue diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 00a2b08a..222ec90c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ This project adheres to `Semantic Versioning `_. * Added ``--max-headers`` to allow setting the max header limit. * Added ``--compress``. * Added ``https`` alias command with ``https://`` as the default scheme. +* Fixed an exception when ``stdin`` was a closed fd. `1.0.3`_ (2019-08-26) diff --git a/README.rst b/README.rst index 0cff7bb6..bb72de7b 100644 --- a/README.rst +++ b/README.rst @@ -126,7 +126,7 @@ and always provides the latest version) is to use `pip`_: Python version -------------- -Starting with version 2.0.0 (currently under development) Python 3.5+ is required. +Starting with version 2.0.0 (currently under development) Python 3.6+ is required. Unstable version diff --git a/httpie/context.py b/httpie/context.py index 3873024d..621fdfa5 100644 --- a/httpie/context.py +++ b/httpie/context.py @@ -1,4 +1,7 @@ import sys +from typing import Union, IO, Optional + + try: import curses except ImportError: @@ -22,7 +25,7 @@ class Environment(object): """ is_windows = is_windows config_dir = DEFAULT_CONFIG_DIR - stdin = sys.stdin + stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791) stdin_isatty = stdin.isatty() if stdin else False stdin_encoding = None stdout = sys.stdout diff --git a/httpie/input.py b/httpie/input.py index a304ee85..102612f3 100644 --- a/httpie/input.py +++ b/httpie/input.py @@ -134,6 +134,7 @@ class HTTPieArgumentParser(ArgumentParser): super(HTTPieArgumentParser, self).__init__(*args, **kwargs) self.env = None self.args = None + self.has_stdin_data = False # noinspection PyMethodOverriding def parse_args(self, env, program_name='http', args=None, namespace=None): diff --git a/tests/test_cli.py b/tests/test_cli.py index d4606f52..8f571f0f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -310,7 +310,7 @@ class TestNoOptions: assert 'GET /get HTTP/1.1' not in r -class TestIgnoreStdin: +class TestStdin: def test_ignore_stdin(self, httpbin): with open(FILE_PATH) as f: @@ -327,6 +327,10 @@ class TestIgnoreStdin: assert r.exit_status == ExitStatus.ERROR assert 'because --ignore-stdin' in r.stderr + def test_stdin_closed(self, httpbin): + r = http(httpbin + '/get', env=MockEnvironment(stdin=None)) + assert HTTP_OK in r + class TestSchemes: