Compare commits

...

25 Commits
0.7.1 ... 0.8.0

Author SHA1 Message Date
43cc3e7ddb Fixed changelog link. 2014-01-25 15:15:16 +01:00
f1224da526 v0.8.0 2014-01-25 15:11:38 +01:00
e0cc63c7eb Cleanup 2014-01-25 15:09:28 +01:00
52dd6adaa3 Updated README. 2014-01-25 15:04:15 +01:00
1aa77017d5 Catch UnicodeDecodeError when embedding file via =@ or :=@. 2014-01-25 14:57:19 +01:00
748a0a480d Update README.rst 2014-01-17 08:57:05 +01:00
01df344a07 Update README.rst 2014-01-17 08:56:24 +01:00
b1074ccb4f Merge pull request #191 from solidsnack/wip-no-auth-in-host-header
Expunge user:pass@... from Host header.
2014-01-08 02:28:19 -08:00
7a84163d1c Merge pull request #192 from thomasleveil/patch-1
fix typo
2014-01-08 02:27:29 -08:00
a31d552d1c fix typo 2014-01-07 14:04:13 +01:00
5a037b2e13 Expunge user:pass@... from Host header.
In verbose mode, the basic auth user and password would show up in colored
output reporting the Host header, as reported in
https://github.com/jkbr/httpie/issues/169
2014-01-06 19:12:33 +00:00
6af42b1827 Added Bitdeli badge. 2013-12-08 11:38:26 +01:00
0e267d8efa Added a link to the httpie-negotiate auth plugin by @ndzou II. 2013-10-09 23:46:55 +02:00
927acc283e Added a link to the httpie-negotiate auth plugin by @ndzou. 2013-10-09 23:44:55 +02:00
817165f5ff Merge pull request #171 from nlf/master
Allow :port style shorthand for localhost.
2013-10-09 13:22:30 -07:00
4fe3deb9d9 add self to authors, update changelog, and mention shorthand in --help output 2013-10-09 13:21:14 -07:00
9034546b80 tweak readme more 2013-10-09 11:37:05 -07:00
2c12fd99f9 tweak readme more 2013-10-09 11:36:01 -07:00
70eb97dece tweak readme to show http requests 2013-10-09 11:34:22 -07:00
8a52bef559 make shorthand parsing more robust, add unit tests and documentation 2013-10-09 11:32:41 -07:00
711168a899 allow :port style shorthand 2013-10-08 22:41:38 -07:00
81c99886fd Update --proxy examples to include URLs to work with Requests v2.0.0.. 2013-09-25 22:02:29 +02:00
2e535d8345 Fixed password prompt. 2013-09-25 00:17:50 +02:00
0bcd4d2fb0 Fixed a bytes/str issue for Python 3. 2013-09-25 00:00:17 +02:00
d5bc564e4f Allow embeding text (=@) and JSON (:=@) files content into request data fields. 2013-09-24 23:41:18 +02:00
8 changed files with 259 additions and 58 deletions

View File

@ -29,3 +29,4 @@ Patches and ideas
* `Davey Shafik <https://github.com/dshafik>`_ * `Davey Shafik <https://github.com/dshafik>`_
* `cido <https://github.com/cido>`_ * `cido <https://github.com/cido>`_
* `Justin Bonnar <https://github.com/jargonjustin>`_ * `Justin Bonnar <https://github.com/jargonjustin>`_
* `Nathan LaFreniere <https://github.com/nlf>`_

View File

@ -121,7 +121,6 @@ See also ``http --help``.
Examples Examples
-------- --------
Custom `HTTP method`_, `HTTP headers`_ and `JSON`_ data: Custom `HTTP method`_, `HTTP headers`_ and `JSON`_ data:
.. code-block:: bash .. code-block:: bash
@ -226,6 +225,42 @@ The only information HTTPie needs to perform a request is a URL.
The default scheme is, somewhat unsurprisingly, ``http://``, The default scheme is, somewhat unsurprisingly, ``http://``,
and can be omitted from the argument ``http example.org`` works just fine. and can be omitted from the argument ``http example.org`` works just fine.
Additionally, curl-like shorthand for localhost is supported.
This means that, for example ``:3000`` would expand to ``http://localhost:3000``
If the port is omitted, then port 80 is assumed.
.. code-block:: bash
$ http :/foo
.. code-block:: http
GET /foo HTTP/1.1
Host: localhost
.. code-block:: bash
$ http :3000/bar
.. code-block:: http
GET /bar HTTP/1.1
Host: localhost:3000
.. code-block:: bash
$ http :
.. code-block:: http
GET / HTTP/1.1
Host: localhost
If find yourself manually constructing URLs with **querystring parameters** If find yourself manually constructing URLs with **querystring parameters**
on the terminal, you may appreciate the ``param==value`` syntax for appending on the terminal, you may appreciate the ``param==value`` syntax for appending
URL parameters so that you don't have to worry about escaping the ``&`` URL parameters so that you don't have to worry about escaping the ``&``
@ -246,14 +281,15 @@ command:
Request Items Request Items
============= =============
There are five different *request item* types that provide a There are a few different *request item* types that provide a
convenient mechanism for specifying HTTP headers, simple JSON and convenient mechanism for specifying HTTP headers, simple JSON and
form data, files, and URL parameters. form data, files, and URL parameters.
They are key/value pairs specified after the URL. All have in They are key/value pairs specified after the URL. All have in
common that they become part of the actual request that is sent and that common that they become part of the actual request that is sent and that
their type is distinguished only by the separator used: their type is distinguished only by the separator used:
``:``, ``=``, ``:=``, ``@``, and ``==``. ``:``, ``=``, ``:=``, ``==``, ``@``, ``=@``, and ``:=@``. The ones with an
``@`` expect a file path as value.
+-----------------------+-----------------------------------------------------+ +-----------------------+-----------------------------------------------------+
| Item Type | Description | | Item Type | Description |
@ -266,16 +302,16 @@ their type is distinguished only by the separator used:
| | The ``==`` separator is used | | | The ``==`` separator is used |
+-----------------------+-----------------------------------------------------+ +-----------------------+-----------------------------------------------------+
| Data Fields | Request data fields to be serialized as a JSON | | Data Fields | Request data fields to be serialized as a JSON |
| ``field=value`` | object (default), or to be form encoded | | ``field=value``, | object (default), or to be form-encoded |
| | (``--form, -f``). | | ``field=@file.txt`` | (``--form, -f``). |
+-----------------------+-----------------------------------------------------+ +-----------------------+-----------------------------------------------------+
| Raw JSON fields | Useful when sending JSON and one or | | Raw JSON fields | Useful when sending JSON and one or |
| ``field:=json`` | more fields need to be a ``Boolean``, ``Number``, | | ``field:=json``, | more fields need to be a ``Boolean``, ``Number``, |
| | nested ``Object``, or an ``Array``, e.g., | | ``field:=@file.json`` | nested ``Object``, or an ``Array``, e.g., |
| | ``meals:='["ham","spam"]'`` or ``pies:=[1,2,3]`` | | | ``meals:='["ham","spam"]'`` or ``pies:=[1,2,3]`` |
| | (note the quotes). | | | (note the quotes). |
+-----------------------+-----------------------------------------------------+ +-----------------------+-----------------------------------------------------+
| Files | Only available with ``--form, -f``. | | Form File Fields | Only available with ``--form, -f``. |
| ``field@/dir/file`` | For example ``screenshot@~/Pictures/img.png``. | | ``field@/dir/file`` | For example ``screenshot@~/Pictures/img.png``. |
| | The presence of a file field results | | | The presence of a file field results |
| | in a ``multipart/form-data`` request. | | | in a ``multipart/form-data`` request. |
@ -285,6 +321,8 @@ You can use ``\`` to escape characters that shouldn't be used as separators
(or parts thereof). For instance, ``foo\==bar`` will become a data key/value (or parts thereof). For instance, ``foo\==bar`` will become a data key/value
pair (``foo=`` and ``bar``) instead of a URL parameter. pair (``foo=`` and ``bar``) instead of a URL parameter.
You can also quote values, e.g. ``foo="bar baz"``.
Note that data fields aren't the only way to specify request data: Note that data fields aren't the only way to specify request data:
`Redirected input`_ allows for passing arbitrary data to be sent with the `Redirected input`_ allows for passing arbitrary data to be sent with the
request. request.
@ -332,11 +370,16 @@ Simple example:
Non-string fields use the ``:=`` separator, which allows you to embed raw JSON Non-string fields use the ``:=`` separator, which allows you to embed raw JSON
into the resulting object: into the resulting object. Text and raw JSON files can also be embedded into
fields using ``=@`` and ``:=@``:
.. code-block:: bash .. code-block:: bash
$ http PUT api.example.com/person/1 name=John age:=29 married:=false hobbies:='["http", "pies"]' $ http PUT api.example.com/person/1 \
name=John \
age:=29 married:=false hobbies:='["http", "pies"]' \ # Raw JSON
description=@about-john.txt \ # Embed text file
bookmarks:=@bookmarks.json # Embed JSON file
.. code-block:: http .. code-block:: http
@ -352,8 +395,12 @@ into the resulting object:
"http", "http",
"pies" "pies"
], ],
"description": "John is a nice guy who likes pies.",
"married": false, "married": false,
"name": "John" "name": "John",
"bookmarks": {
"HTTPie": "http://httpie.org",
}
} }
@ -383,7 +430,7 @@ Regular Forms
.. code-block:: bash .. code-block:: bash
$ http --form POST api.example.org/person/1 name='John Smith' email=john@example.org $ http --form POST api.example.org/person/1 name='John Smith' email=john@example.org cv=@~/Documents/cv.txt
.. code-block:: http .. code-block:: http
@ -391,7 +438,7 @@ Regular Forms
POST /person/1 HTTP/1.1 POST /person/1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=utf-8 Content-Type: application/x-www-form-urlencoded; charset=utf-8
name=John+Smith&email=john%40example.org name=John+Smith&email=john%40example.org&cv=John's+CV+...
----------------- -----------------
@ -416,6 +463,9 @@ submitted:
<input type="file" name="cv" /> <input type="file" name="cv" />
</form> </form>
Note that ``@`` is used to simulate a file upload form field, whereas
``=@`` just embeds the file content as a regular text field value.
============ ============
HTTP Headers HTTP Headers
@ -519,8 +569,9 @@ Authorization information from your ``~/.netrc`` file is honored as well:
Auth Plugins Auth Plugins
------------ ------------
* `httpie-ntlm <https://github.com/jkbr/httpie-ntlm>`_ * `httpie-oauth <https://github.com/jkbr/httpie-oauth>`_: OAuth
* `httpie-oauth <https://github.com/jkbr/httpie-oauth>`_ * `httpie-ntlm <https://github.com/jkbr/httpie-ntlm>`_: NTLM (NT LAN Manager)
* `httpie-negotiate <https://github.com/ndzou/httpie-negotiate>`_: SPNEGO (GSS Negotiate)
======= =======
@ -532,7 +583,7 @@ protocol (which is included in the value in case of redirects across protocols):
.. code-block:: bash .. code-block:: bash
$ http --proxy=http:10.10.1.10:3128 --proxy=https:10.10.1.10:1080 example.org $ http --proxy=http:http://10.10.1.10:3128 --proxy=https:https://10.10.1.10:1080 example.org
With Basic authentication: With Basic authentication:
@ -550,8 +601,8 @@ In your ``~/.bash_profile``:
.. code-block:: bash .. code-block:: bash
export HTTP_PROXY=10.10.1.10:3128 export HTTP_PROXY=http://10.10.1.10:3128
export HTTPS_PROXY=10.10.1.10:1080 export HTTPS_PROXY=https://10.10.1.10:1080
export NO_PROXY=localhost,example.com export NO_PROXY=localhost,example.com
@ -1197,7 +1248,7 @@ See `claudiatd/httpie-artwork`_
Authors Authors
======= =======
`Jakub Roztocil`_ (`@jakubroztocil`_) created HTTPie and `these fine people`_ `Jakub Roztocil`_ (`@jkbrzt`_) created HTTPie and `these fine people`_
have contributed. have contributed.
======= =======
@ -1213,7 +1264,13 @@ Changelog
*You can click a version name to see a diff with the previous one.* *You can click a version name to see a diff with the previous one.*
* `0.8.0-dev`_ * `0.9.0-dev`_
* `0.8.0`_ (2014-01-25)
* Added ``field=@file.txt`` and ``field:=@file.json`` for embedding
the contents of text and JSON files into request data.
* Added curl-style shorthand for localhost.
* Fixed request ``Host`` header value output so that it doesn't contain
credentials, if included in the URL.
* `0.7.1`_ (2013-09-24) * `0.7.1`_ (2013-09-24)
* Added ``--ignore-stdin``. * Added ``--ignore-stdin``.
* Added support for auth plugins. * Added support for auth plugins.
@ -1311,6 +1368,14 @@ Changelog
* `0.1.6`_ (2012-03-04) * `0.1.6`_ (2012-03-04)
------------
.. image:: https://d2weczhvl823v0.cloudfront.net/jkbr/httpie/trend.png
:target: https://bitdeli.com/free
:alt: Bitdeli Badge
.. _Requests: http://python-requests.org .. _Requests: http://python-requests.org
.. _Pygments: http://pygments.org/ .. _Pygments: http://pygments.org/
.. _pip: http://www.pip-installer.org/en/latest/index.html .. _pip: http://www.pip-installer.org/en/latest/index.html
@ -1321,8 +1386,8 @@ Changelog
.. _Debian: http://packages.debian.org/httpie .. _Debian: http://packages.debian.org/httpie
.. _the repository: https://github.com/jkbr/httpie .. _the repository: https://github.com/jkbr/httpie
.. _these fine people: https://github.com/jkbr/httpie/contributors .. _these fine people: https://github.com/jkbr/httpie/contributors
.. _Jakub Roztocil: http://roztocil.name .. _Jakub Roztocil: http://subtleapps.com
.. _@jakubroztocil: https://twitter.com/jakubroztocil .. _@jkbrzt: https://twitter.com/jkbrzt
.. _existing issues: https://github.com/jkbr/httpie/issues?state=open .. _existing issues: https://github.com/jkbr/httpie/issues?state=open
.. _claudiatd/httpie-artwork: https://github.com/claudiatd/httpie-artwork .. _claudiatd/httpie-artwork: https://github.com/claudiatd/httpie-artwork
.. _0.1.6: https://github.com/jkbr/httpie/compare/0.1.4...0.1.6 .. _0.1.6: https://github.com/jkbr/httpie/compare/0.1.4...0.1.6
@ -1339,6 +1404,7 @@ Changelog
.. _0.5.1: https://github.com/jkbr/httpie/compare/0.5.0...0.5.1 .. _0.5.1: https://github.com/jkbr/httpie/compare/0.5.0...0.5.1
.. _0.6.0: https://github.com/jkbr/httpie/compare/0.5.1...0.6.0 .. _0.6.0: https://github.com/jkbr/httpie/compare/0.5.1...0.6.0
.. _0.7.1: https://github.com/jkbr/httpie/compare/0.6.0...0.7.1 .. _0.7.1: https://github.com/jkbr/httpie/compare/0.6.0...0.7.1
.. _0.8.0-dev: https://github.com/jkbr/httpie/compare/0.7.1...master .. _0.8.0: https://github.com/jkbr/httpie/compare/0.7.1...0.8.0
.. _0.9.0-dev: https://github.com/jkbr/httpie/compare/0.8.0...master
.. _AUTHORS.rst: https://github.com/jkbr/httpie/blob/master/AUTHORS.rst .. _AUTHORS.rst: https://github.com/jkbr/httpie/blob/master/AUTHORS.rst
.. _LICENSE: https://github.com/jkbr/httpie/blob/master/LICENSE .. _LICENSE: https://github.com/jkbr/httpie/blob/master/LICENSE

View File

@ -3,7 +3,7 @@ HTTPie - a CLI, cURL-like tool for humans.
""" """
__author__ = 'Jakub Roztocil' __author__ = 'Jakub Roztocil'
__version__ = '0.7.1' __version__ = '0.8.0'
__licence__ = 'BSD' __licence__ = 'BSD'

View File

@ -15,7 +15,7 @@ from .plugins import plugin_manager
from .sessions import DEFAULT_SESSIONS_DIR from .sessions import DEFAULT_SESSIONS_DIR
from .output import AVAILABLE_STYLES, DEFAULT_STYLE from .output import AVAILABLE_STYLES, DEFAULT_STYLE
from .input import (Parser, AuthCredentialsArgType, KeyValueArgType, from .input import (Parser, AuthCredentialsArgType, KeyValueArgType,
SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS, SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ALL_ITEMS,
OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD, OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD,
OUT_RESP_BODY, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT, OUT_RESP_BODY, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT,
PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, SessionNameValidator) PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, SessionNameValidator)
@ -88,13 +88,18 @@ positional.add_argument(
help=""" help="""
The scheme defaults to 'http://' if the URL does not include one. The scheme defaults to 'http://' if the URL does not include one.
You can also use a shorthand for localhost
$ http :3000 # => http://localhost:3000
$ http :/foo # => http://localhost/foo
""" """
) )
positional.add_argument( positional.add_argument(
'items', 'items',
metavar='REQUEST ITEM', metavar='REQUEST_ITEM',
nargs=ZERO_OR_MORE, nargs=ZERO_OR_MORE,
type=KeyValueArgType(*SEP_GROUP_ITEMS), type=KeyValueArgType(*SEP_GROUP_ALL_ITEMS),
help=r""" help=r"""
Optional key-value pairs to be included in the request. The separator used Optional key-value pairs to be included in the request. The separator used
determines the type: determines the type:
@ -112,13 +117,21 @@ positional.add_argument(
name=HTTPie language=Python description='CLI HTTP client' name=HTTPie language=Python description='CLI HTTP client'
':=' Non-string JSON data fields (only with --json, -j):
awesome:=true amount:=42 colors:='["red", "green", "blue"]'
'@' Form file fields (only with --form, -f): '@' Form file fields (only with --form, -f):
cs@~/Documents/CV.pdf cs@~/Documents/CV.pdf
':=' Non-string JSON data fields (only with --json, -j): '=@' A data field like '=', but takes a file path and embeds its content:
awesome:=true amount:=42 colors:='["red", "green", "blue"]' essay=@Documents/essay.txt
':=@' A raw JSON field like ':=', but takes a file path and embeds its content:
package:=@./package.json
You can use a backslash to escape a colliding separator in the field name: You can use a backslash to escape a colliding separator in the field name:
@ -186,7 +199,7 @@ output_processing.add_argument(
default=DEFAULT_STYLE, default=DEFAULT_STYLE,
choices=AVAILABLE_STYLES, choices=AVAILABLE_STYLES,
help=""" help="""
Output coloring style (default is "{default}"). On of: Output coloring style (default is "{default}"). One of:
{available} {available}
@ -422,11 +435,12 @@ network.add_argument(
'--proxy', '--proxy',
default=[], default=[],
action='append', action='append',
metavar='PROTOCOL:HOST', metavar='PROTOCOL:PROXY_URL',
type=KeyValueArgType(SEP_PROXY), type=KeyValueArgType(SEP_PROXY),
help=""" help="""
String mapping protocol to the URL of the proxy (e.g. http:foo.bar:3128). String mapping protocol to the URL of the proxy
You can specify multiple proxies with different protocols. (e.g. http:http://foo.bar:3128). You can specify multiple proxies with
different protocols.
""" """
) )

View File

@ -6,7 +6,7 @@ import sys
import re import re
import json import json
import mimetypes import mimetypes
from getpass import getpass import getpass
from io import BytesIO from io import BytesIO
#noinspection PyCompatibility #noinspection PyCompatibility
from argparse import ArgumentParser, ArgumentTypeError, ArgumentError from argparse import ArgumentParser, ArgumentTypeError, ArgumentError
@ -37,22 +37,40 @@ SEP_PROXY = ':'
SEP_DATA = '=' SEP_DATA = '='
SEP_DATA_RAW_JSON = ':=' SEP_DATA_RAW_JSON = ':='
SEP_FILES = '@' SEP_FILES = '@'
SEP_DATA_EMBED_FILE = '=@'
SEP_DATA_EMBED_RAW_JSON_FILE = ':=@'
SEP_QUERY = '==' SEP_QUERY = '=='
# Separators that become request data # Separators that become request data
SEP_GROUP_DATA_ITEMS = frozenset([ SEP_GROUP_DATA_ITEMS = frozenset([
SEP_DATA, SEP_DATA,
SEP_DATA_RAW_JSON, SEP_DATA_RAW_JSON,
SEP_FILES SEP_FILES,
SEP_DATA_EMBED_FILE,
SEP_DATA_EMBED_RAW_JSON_FILE
])
# Separators for items whose value is a filename to be embedded
SEP_GROUP_DATA_EMBED_ITEMS = frozenset([
SEP_DATA_EMBED_FILE,
SEP_DATA_EMBED_RAW_JSON_FILE,
])
# Separators for raw JSON items
SEP_GROUP_RAW_JSON_ITEMS = frozenset([
SEP_DATA_RAW_JSON,
SEP_DATA_EMBED_RAW_JSON_FILE,
]) ])
# Separators allowed in ITEM arguments # Separators allowed in ITEM arguments
SEP_GROUP_ITEMS = frozenset([ SEP_GROUP_ALL_ITEMS = frozenset([
SEP_HEADERS, SEP_HEADERS,
SEP_QUERY, SEP_QUERY,
SEP_DATA, SEP_DATA,
SEP_DATA_RAW_JSON, SEP_DATA_RAW_JSON,
SEP_FILES SEP_FILES,
SEP_DATA_EMBED_FILE,
SEP_DATA_EMBED_RAW_JSON_FILE,
]) ])
@ -120,7 +138,18 @@ class Parser(ArgumentParser):
if not (self.args.url.startswith((HTTP, HTTPS))): if not (self.args.url.startswith((HTTP, HTTPS))):
# Default to 'https://' if invoked as `https args`. # Default to 'https://' if invoked as `https args`.
scheme = HTTPS if self.env.progname == 'https' else HTTP scheme = HTTPS if self.env.progname == 'https' else HTTP
self.args.url = scheme + self.args.url
# See if we're using curl style shorthand for localhost (:3000/foo)
shorthand = re.match(r'^:(?!:)(\d*)(/?.*)$', self.args.url)
if shorthand:
port = shorthand.group(1)
rest = shorthand.group(2)
self.args.url = scheme + 'localhost'
if port:
self.args.url += ':' + port
self.args.url += rest
else:
self.args.url = scheme + self.args.url
self._process_auth() self._process_auth()
return self.args return self.args
@ -257,7 +286,7 @@ class Parser(ArgumentParser):
# Parse the URL as an ITEM and store it as the first ITEM arg. # Parse the URL as an ITEM and store it as the first ITEM arg.
self.args.items.insert( self.args.items.insert(
0, 0,
KeyValueArgType(*SEP_GROUP_ITEMS).__call__(self.args.url) KeyValueArgType(*SEP_GROUP_ALL_ITEMS).__call__(self.args.url)
) )
except ArgumentTypeError as e: except ArgumentTypeError as e:
@ -558,9 +587,7 @@ def parse_items(items, data=None, headers=None, files=None, params=None):
params = ParamDict() params = ParamDict()
for item in items: for item in items:
value = item.value value = item.value
key = item.key
if item.sep == SEP_HEADERS: if item.sep == SEP_HEADERS:
target = headers target = headers
@ -572,21 +599,34 @@ def parse_items(items, data=None, headers=None, files=None, params=None):
value = (os.path.basename(value), value = (os.path.basename(value),
BytesIO(f.read())) BytesIO(f.read()))
except IOError as e: except IOError as e:
raise ParseError( raise ParseError('"%s": %s' % (item.orig, e))
'Invalid argument "%s": %s' % (item.orig, e))
target = files target = files
elif item.sep in [SEP_DATA, SEP_DATA_RAW_JSON]: elif item.sep in SEP_GROUP_DATA_ITEMS:
if item.sep == SEP_DATA_RAW_JSON:
if item.sep in SEP_GROUP_DATA_EMBED_ITEMS:
try: try:
value = json.loads(item.value) with open(os.path.expanduser(value), 'rb') as f:
except ValueError: value = f.read().decode('utf8')
raise ParseError('"%s" is not valid JSON' % item.orig) except IOError as e:
raise ParseError('"%s": %s' % (item.orig, e))
except UnicodeDecodeError:
raise ParseError(
'"%s": cannot embed the content of "%s",'
' not a UTF8 or ASCII-encoded text file'
% (item.orig, item.value)
)
if item.sep in SEP_GROUP_RAW_JSON_ITEMS:
try:
value = json.loads(value)
except ValueError as e:
raise ParseError('"%s": %s' % (item.orig, e))
target = data target = data
else: else:
raise TypeError(item) raise TypeError(item)
target[key] = value target[item.key] = value
return headers, data, files, params return headers, data, files, params

View File

@ -155,7 +155,7 @@ class HTTPRequest(HTTPMessage):
headers = dict(self._orig.headers) headers = dict(self._orig.headers)
if 'Host' not in headers: if 'Host' not in headers:
headers['Host'] = url.netloc headers['Host'] = url.netloc.split('@')[-1]
headers = ['%s: %s' % (name, value) headers = ['%s: %s' % (name, value)
for name, value in headers.items()] for name, value in headers.items()]

3
tests/fixtures/test.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"hello": "world"
}

View File

@ -64,6 +64,7 @@ sys.path.insert(0, os.path.realpath(os.path.join(TESTS_ROOT, '..')))
from httpie import ExitStatus from httpie import ExitStatus
from httpie import input from httpie import input
from httpie.cli import parser
from httpie.models import Environment from httpie.models import Environment
from httpie.core import main from httpie.core import main
from httpie.output import BINARY_SUPPRESSED_NOTICE from httpie.output import BINARY_SUPPRESSED_NOTICE
@ -101,15 +102,22 @@ def patharg(path):
FILE_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file.txt') FILE_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file.txt')
FILE2_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file2.txt') FILE2_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file2.txt')
BIN_FILE_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file.bin') BIN_FILE_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'file.bin')
JSON_FILE_PATH = os.path.join(TESTS_ROOT, 'fixtures', 'test.json')
FILE_PATH_ARG = patharg(FILE_PATH) FILE_PATH_ARG = patharg(FILE_PATH)
FILE2_PATH_ARG = patharg(FILE2_PATH) FILE2_PATH_ARG = patharg(FILE2_PATH)
BIN_FILE_PATH_ARG = patharg(BIN_FILE_PATH) BIN_FILE_PATH_ARG = patharg(BIN_FILE_PATH)
JSON_FILE_PATH_ARG = patharg(JSON_FILE_PATH)
with open(FILE_PATH) as f: with open(FILE_PATH) as f:
# Strip because we don't want new lines in the data so that we can
# easily count occurrences also when embedded in JSON (where the new
# line would be escaped).
FILE_CONTENT = f.read().strip() FILE_CONTENT = f.read().strip()
with open(BIN_FILE_PATH, 'rb') as f: with open(BIN_FILE_PATH, 'rb') as f:
BIN_FILE_CONTENT = f.read() BIN_FILE_CONTENT = f.read()
with open(JSON_FILE_PATH, 'rb') as f:
JSON_FILE_CONTENT = f.read()
def httpbin(path): def httpbin(path):
@ -1172,11 +1180,7 @@ class ItemParsingTest(BaseTestCase):
def setUp(self): def setUp(self):
self.key_value_type = input.KeyValueArgType( self.key_value_type = input.KeyValueArgType(
input.SEP_HEADERS, *input.SEP_GROUP_ALL_ITEMS
input.SEP_QUERY,
input.SEP_DATA,
input.SEP_DATA_RAW_JSON,
input.SEP_FILES,
) )
def test_invalid_items(self): def test_invalid_items(self):
@ -1223,26 +1227,99 @@ class ItemParsingTest(BaseTestCase):
self.key_value_type('eh:'), self.key_value_type('eh:'),
self.key_value_type('ed='), self.key_value_type('ed='),
self.key_value_type('bool:=true'), self.key_value_type('bool:=true'),
self.key_value_type('test-file@%s' % FILE_PATH_ARG), self.key_value_type('file@' + FILE_PATH_ARG),
self.key_value_type('query==value'), self.key_value_type('query==value'),
self.key_value_type('string-embed=@' + FILE_PATH_ARG),
self.key_value_type('raw-json-embed:=@' + JSON_FILE_PATH_ARG),
]) ])
# Parsed headers
# `requests.structures.CaseInsensitiveDict` => `dict` # `requests.structures.CaseInsensitiveDict` => `dict`
headers = dict(headers._store.values()) headers = dict(headers._store.values())
self.assertDictEqual(headers, { self.assertDictEqual(headers, {
'header': 'value', 'header': 'value',
'eh': '' 'eh': ''
}) })
self.assertDictEqual(data, {
# Parsed data
raw_json_embed = data.pop('raw-json-embed')
self.assertDictEqual(raw_json_embed, json.loads(
JSON_FILE_CONTENT.decode('utf8')))
data['string-embed'] = data['string-embed'].strip()
self.assertDictEqual(dict(data), {
"ed": "", "ed": "",
"string": "value", "string": "value",
"bool": True, "bool": True,
"list": ["a", 1, {}, False], "list": ["a", 1, {}, False],
"obj": {"a": "b"}, "obj": {"a": "b"},
"string-embed": FILE_CONTENT,
}) })
# Parsed query string parameters
self.assertDictEqual(params, { self.assertDictEqual(params, {
'query': 'value', 'query': 'value',
}) })
self.assertIn('test-file', files)
# Parsed file fields
self.assertIn('file', files)
self.assertEqual(files['file'][1].read().strip().decode('utf8'),
FILE_CONTENT)
class CLIParserTestCase(unittest.TestCase):
def test_expand_localhost_shorthand(self):
args = parser.parse_args(args=[':'], env=TestEnvironment())
self.assertEqual(args.url, 'http://localhost')
def test_expand_localhost_shorthand_with_slash(self):
args = parser.parse_args(args=[':/'], env=TestEnvironment())
self.assertEqual(args.url, 'http://localhost/')
def test_expand_localhost_shorthand_with_port(self):
args = parser.parse_args(args=[':3000'], env=TestEnvironment())
self.assertEqual(args.url, 'http://localhost:3000')
def test_expand_localhost_shorthand_with_path(self):
args = parser.parse_args(args=[':/path'], env=TestEnvironment())
self.assertEqual(args.url, 'http://localhost/path')
def test_expand_localhost_shorthand_with_port_and_slash(self):
args = parser.parse_args(args=[':3000/'], env=TestEnvironment())
self.assertEqual(args.url, 'http://localhost:3000/')
def test_expand_localhost_shorthand_with_port_and_path(self):
args = parser.parse_args(args=[':3000/path'], env=TestEnvironment())
self.assertEqual(args.url, 'http://localhost:3000/path')
def test_dont_expand_shorthand_ipv6_as_shorthand(self):
args = parser.parse_args(args=['::1'], env=TestEnvironment())
self.assertEqual(args.url, 'http://::1')
def test_dont_expand_longer_ipv6_as_shorthand(self):
args = parser.parse_args(
args=['::ffff:c000:0280'],
env=TestEnvironment()
)
self.assertEqual(args.url, 'http://::ffff:c000:0280')
def test_dont_expand_full_ipv6_as_shorthand(self):
args = parser.parse_args(
args=['0000:0000:0000:0000:0000:0000:0000:0001'],
env=TestEnvironment()
)
self.assertEqual(
args.url,
'http://0000:0000:0000:0000:0000:0000:0000:0001'
)
class ArgumentParserTestCase(unittest.TestCase): class ArgumentParserTestCase(unittest.TestCase):