diff --git a/httpie/core.py b/httpie/core.py index 95e21d0a..9c487b27 100644 --- a/httpie/core.py +++ b/httpie/core.py @@ -85,7 +85,7 @@ def get_response(args, env): except Exception as e: if args.debug: raise - env.stderr.write(str(e.message) + '\n') + env.stderr.write(str(repr(e) + '\n')) sys.exit(1) diff --git a/httpie/input.py b/httpie/input.py index ba71ab10..b73d70db 100644 --- a/httpie/input.py +++ b/httpie/input.py @@ -103,7 +103,7 @@ class Parser(argparse.ArgumentParser): self._parse_items(args) if not env.stdin_isatty: - self._body_from_file(args, env.stdin.read()) + self._body_from_file(args, env.stdin) if args.auth and not args.auth.has_password(): # Stdin already read (if not a tty) so it's save to prompt. @@ -129,12 +129,16 @@ class Parser(argparse.ArgumentParser): super(Parser, self)._print_message(message, file) - def _body_from_file(self, args, data): - """There can only be one source of request data.""" + def _body_from_file(self, args, fd): + """There can only be one source of request data. + + Bytes are always read. + + """ if args.data: self.error('Request body (from stdin or a file) and request ' 'data (key=value) cannot be mixed.') - args.data = data + args.data = getattr(fd, 'buffer', fd).read() def _guess_method(self, args, env): """Set `args.method` if not specified to either POST or GET @@ -201,9 +205,9 @@ class Parser(argparse.ArgumentParser): 'Invalid file fields (perhaps you meant --form?): %s' % ','.join(file_fields)) - fn, data = args.files[''] + fn, fd = args.files[''] args.files = {} - self._body_from_file(args, data) + self._body_from_file(args, fd) if 'Content-Type' not in args.headers: mime, encoding = mimetypes.guess_type(fn, strict=False) if mime: @@ -420,8 +424,8 @@ def parse_items(items, data=None, headers=None, files=None, params=None): target = params elif item.sep == SEP_FILES: try: - with open(os.path.expanduser(value)) as f: - value = (os.path.basename(f.name), f.read()) + value = (os.path.basename(value), + open(os.path.expanduser(value), 'rb')) except IOError as e: raise ParseError( 'Invalid argument "%s": %s' % (item.orig, e)) diff --git a/httpie/models.py b/httpie/models.py index 4b12d8c4..5f78fc41 100644 --- a/httpie/models.py +++ b/httpie/models.py @@ -120,6 +120,10 @@ class HTTPMessage(object): # Body if request.files: + # TODO: would be nice if we didn't need to encode the files again + for fn, fd in request.files.values(): + # Rewind the files as they have already been read before. + fd.seek(0) body, _ = request._encode_files(request.files) else: try: diff --git a/tests/fixtures/file.bin b/tests/fixtures/file.bin new file mode 100644 index 00000000..ee7c943a Binary files /dev/null and b/tests/fixtures/file.bin differ diff --git a/tests/file.txt b/tests/fixtures/file.txt similarity index 100% rename from tests/file.txt rename to tests/fixtures/file.txt diff --git a/tests/file2.txt b/tests/fixtures/file2.txt similarity index 100% rename from tests/file2.txt rename to tests/fixtures/file2.txt diff --git a/tests/tests.py b/tests/tests.py index d3d0cfe8..6006f2ad 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -51,10 +51,16 @@ from httpie.input import ParseError HTTPBIN_URL = os.environ.get('HTTPBIN_URL', 'http://httpbin.org') -TEST_FILE_PATH = os.path.join(TESTS_ROOT, 'file.txt') -TEST_FILE2_PATH = os.path.join(TESTS_ROOT, 'file2.txt') +TEST_FILE_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file.txt') +TEST_FILE2_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file2.txt') + with open(TEST_FILE_PATH) as f: TEST_FILE_CONTENT = f.read().strip() + +TEST_BIN_FILE_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file.bin') +with open(TEST_BIN_FILE_PATH, 'rb') as f: + TEST_BIN_FILE_CONTENT = f.read() + TERMINAL_COLOR_PRESENCE_CHECK = '\x1b[' @@ -82,7 +88,6 @@ def http(*args, **kwargs): and return a unicode response. """ - if 'env' not in kwargs: # Ensure that we have terminal by default (needed for Travis). kwargs['env'] = Environment( @@ -94,7 +99,11 @@ def http(*args, **kwargs): stdout = kwargs['env'].stdout = tempfile.TemporaryFile('w+b') stderr = kwargs['env'].stderr = tempfile.TemporaryFile('w+t') - exit_status = main(args=['--debug'] + list(args), **kwargs) + try: + exit_status = main(args=['--debug'] + list(args), **kwargs) + except (Exception, SystemExit) as e: + sys.stderr.write(stderr.read()) + raise stdout.seek(0) stderr.seek(0) @@ -564,7 +573,54 @@ class MultipartFormDataFileUploadTest(BaseTestCase): self.assertIn('"foo": "bar"', r) -class TestBinaryResponses(BaseTestCase): +class BinaryRequestDataTest(BaseTestCase): + + def test_binary_stdin(self): + env = Environment( + stdin=open(TEST_BIN_FILE_PATH, 'rb'), + stdin_isatty=False, + stdout_isatty=False + ) + r = http( + '--print=B', + 'POST', + httpbin('/post'), + env=env, + ) + self.assertEqual(r, TEST_BIN_FILE_CONTENT) + + def test_binary_file_path(self): + env = Environment( + stdin_isatty=True, + stdout_isatty=False + ) + r = http( + '--print=B', + 'POST', + httpbin('/post'), + '@' + TEST_BIN_FILE_PATH, + env=env, + ) + + self.assertEqual(r, TEST_BIN_FILE_CONTENT) + + def test_binary_file_form(self): + env = Environment( + stdin_isatty=True, + stdout_isatty=False + ) + r = http( + '--print=B', + '--form', + 'POST', + httpbin('/post'), + 'test@' + TEST_BIN_FILE_PATH, + env=env, + ) + self.assertIn(bytes(TEST_BIN_FILE_CONTENT), bytes(r)) + + +class BinaryResponseDataTest(BaseTestCase): url = 'http://www.google.com/favicon.ico'