mirror of
https://github.com/httpie/cli.git
synced 2024-11-22 07:43:20 +01:00
Custom file upload MIME type (#927)
* Support curl-like syntax for custom MIME type for files In order to specify a custom MIME type for file uploads, a syntax similar to that used by cURL is used so that http -F test_file@/path/to/file.bin;type=application/zip https://... forwards the user-provided file type if provided, otherwise falling back to the usual guesswork out of the file extension.
This commit is contained in:
parent
492687b0da
commit
c4627cc882
@ -13,6 +13,7 @@ This project adheres to `Semantic Versioning <https://semver.org/>`_.
|
|||||||
* Added ``--ciphers`` to allow configuring OpenSSL ciphers (`#870`_).
|
* Added ``--ciphers`` to allow configuring OpenSSL ciphers (`#870`_).
|
||||||
* Added support for ``$XDG_CONFIG_HOME`` (`#920`_).
|
* Added support for ``$XDG_CONFIG_HOME`` (`#920`_).
|
||||||
* Fixed built-in plugins-related circular imports (`#925`_).
|
* Fixed built-in plugins-related circular imports (`#925`_).
|
||||||
|
* Fixed custom content types for each multipart uploaded file (`#668`_).
|
||||||
|
|
||||||
|
|
||||||
`2.1.0`_ (2020-04-18)
|
`2.1.0`_ (2020-04-18)
|
||||||
@ -441,3 +442,4 @@ This project adheres to `Semantic Versioning <https://semver.org/>`_.
|
|||||||
.. _#895: https://github.com/jakubroztocil/httpie/issues/895
|
.. _#895: https://github.com/jakubroztocil/httpie/issues/895
|
||||||
.. _#920: https://github.com/jakubroztocil/httpie/issues/920
|
.. _#920: https://github.com/jakubroztocil/httpie/issues/920
|
||||||
.. _#925: https://github.com/jakubroztocil/httpie/issues/925
|
.. _#925: https://github.com/jakubroztocil/httpie/issues/925
|
||||||
|
.. _#668: https://github.com/jakubroztocil/httpie/issues/668
|
||||||
|
@ -683,6 +683,13 @@ submitted:
|
|||||||
Note that ``@`` is used to simulate a file upload form field, whereas
|
Note that ``@`` is used to simulate a file upload form field, whereas
|
||||||
``=@`` just embeds the file content as a regular text field value.
|
``=@`` just embeds the file content as a regular text field value.
|
||||||
|
|
||||||
|
When uploading files, their content type is inferred from the file name. You can manually
|
||||||
|
override the inferred content type:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ http -f POST httpbin.org/post name='John Smith' cv@'~/files/data.bin;type=application/pdf'
|
||||||
|
|
||||||
|
|
||||||
HTTP headers
|
HTTP headers
|
||||||
============
|
============
|
||||||
|
@ -24,6 +24,7 @@ SEPARATOR_PROXY = ':'
|
|||||||
SEPARATOR_DATA_STRING = '='
|
SEPARATOR_DATA_STRING = '='
|
||||||
SEPARATOR_DATA_RAW_JSON = ':='
|
SEPARATOR_DATA_RAW_JSON = ':='
|
||||||
SEPARATOR_FILE_UPLOAD = '@'
|
SEPARATOR_FILE_UPLOAD = '@'
|
||||||
|
SEPARATOR_FILE_UPLOAD_TYPE = ';type=' # in already parsed file upload path only
|
||||||
SEPARATOR_DATA_EMBED_FILE_CONTENTS = '=@'
|
SEPARATOR_DATA_EMBED_FILE_CONTENTS = '=@'
|
||||||
SEPARATOR_DATA_EMBED_RAW_JSON_FILE = ':=@'
|
SEPARATOR_DATA_EMBED_RAW_JSON_FILE = ':=@'
|
||||||
SEPARATOR_QUERY_PARAM = '=='
|
SEPARATOR_QUERY_PARAM = '=='
|
||||||
|
@ -113,6 +113,7 @@ positional.add_argument(
|
|||||||
'@' Form file fields (only with --form, -f):
|
'@' Form file fields (only with --form, -f):
|
||||||
|
|
||||||
cs@~/Documents/CV.pdf
|
cs@~/Documents/CV.pdf
|
||||||
|
cv@'~/Documents/CV.pdf;type=application/pdf'
|
||||||
|
|
||||||
'=@' A data field like '=', but takes a file path and embeds its content:
|
'=@' A data field like '=', but takes a file path and embeds its content:
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ from httpie.cli.argtypes import KeyValueArg
|
|||||||
from httpie.cli.constants import (
|
from httpie.cli.constants import (
|
||||||
SEPARATOR_DATA_EMBED_FILE_CONTENTS, SEPARATOR_DATA_EMBED_RAW_JSON_FILE,
|
SEPARATOR_DATA_EMBED_FILE_CONTENTS, SEPARATOR_DATA_EMBED_RAW_JSON_FILE,
|
||||||
SEPARATOR_DATA_RAW_JSON, SEPARATOR_DATA_STRING, SEPARATOR_FILE_UPLOAD,
|
SEPARATOR_DATA_RAW_JSON, SEPARATOR_DATA_STRING, SEPARATOR_FILE_UPLOAD,
|
||||||
SEPARATOR_HEADER, SEPARATOR_HEADER_EMPTY, SEPARATOR_QUERY_PARAM,
|
SEPARATOR_FILE_UPLOAD_TYPE, SEPARATOR_HEADER, SEPARATOR_HEADER_EMPTY,
|
||||||
|
SEPARATOR_QUERY_PARAM,
|
||||||
)
|
)
|
||||||
from httpie.cli.dicts import (
|
from httpie.cli.dicts import (
|
||||||
RequestDataDict, RequestFilesDict, RequestHeadersDict, RequestJSONDataDict,
|
RequestDataDict, RequestFilesDict, RequestHeadersDict, RequestJSONDataDict,
|
||||||
@ -95,7 +96,10 @@ def process_query_param_arg(arg: KeyValueArg) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def process_file_upload_arg(arg: KeyValueArg) -> Tuple[str, IO, str]:
|
def process_file_upload_arg(arg: KeyValueArg) -> Tuple[str, IO, str]:
|
||||||
filename = arg.value
|
parts = arg.value.split(SEPARATOR_FILE_UPLOAD_TYPE)
|
||||||
|
filename = parts[0]
|
||||||
|
mime_type = parts[1] if len(parts) > 1 else None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(os.path.expanduser(filename), 'rb') as f:
|
with open(os.path.expanduser(filename), 'rb') as f:
|
||||||
contents = f.read()
|
contents = f.read()
|
||||||
@ -104,7 +108,7 @@ def process_file_upload_arg(arg: KeyValueArg) -> Tuple[str, IO, str]:
|
|||||||
return (
|
return (
|
||||||
os.path.basename(filename),
|
os.path.basename(filename),
|
||||||
BytesIO(contents),
|
BytesIO(contents),
|
||||||
get_content_type(filename),
|
mime_type or get_content_type(filename),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,27 +17,37 @@ class TestMultipartFormDataFileUpload:
|
|||||||
|
|
||||||
def test_upload_ok(self, httpbin):
|
def test_upload_ok(self, httpbin):
|
||||||
r = http('--form', '--verbose', 'POST', httpbin.url + '/post',
|
r = http('--form', '--verbose', 'POST', httpbin.url + '/post',
|
||||||
'test-file@%s' % FILE_PATH_ARG, 'foo=bar')
|
f'test-file@{FILE_PATH_ARG}', 'foo=bar')
|
||||||
assert HTTP_OK in r
|
assert HTTP_OK in r
|
||||||
assert 'Content-Disposition: form-data; name="foo"' in r
|
assert 'Content-Disposition: form-data; name="foo"' in r
|
||||||
assert 'Content-Disposition: form-data; name="test-file";' \
|
assert 'Content-Disposition: form-data; name="test-file";' \
|
||||||
' filename="%s"' % os.path.basename(FILE_PATH) in r
|
f' filename="{os.path.basename(FILE_PATH)}"' in r
|
||||||
assert FILE_CONTENT in r
|
assert FILE_CONTENT in r
|
||||||
assert '"foo": "bar"' in r
|
assert '"foo": "bar"' in r
|
||||||
assert 'Content-Type: text/plain' in r
|
assert 'Content-Type: text/plain' in r
|
||||||
|
|
||||||
def test_upload_multiple_fields_with_the_same_name(self, httpbin):
|
def test_upload_multiple_fields_with_the_same_name(self, httpbin):
|
||||||
r = http('--form', '--verbose', 'POST', httpbin.url + '/post',
|
r = http('--form', '--verbose', 'POST', httpbin.url + '/post',
|
||||||
'test-file@%s' % FILE_PATH_ARG,
|
f'test-file@{FILE_PATH_ARG}',
|
||||||
'test-file@%s' % FILE_PATH_ARG)
|
f'test-file@{FILE_PATH_ARG}')
|
||||||
assert HTTP_OK in r
|
assert HTTP_OK in r
|
||||||
assert r.count('Content-Disposition: form-data; name="test-file";'
|
assert r.count('Content-Disposition: form-data; name="test-file";'
|
||||||
' filename="%s"' % os.path.basename(FILE_PATH)) == 2
|
f' filename="{os.path.basename(FILE_PATH)}"') == 2
|
||||||
# Should be 4, but is 3 because httpbin
|
# Should be 4, but is 3 because httpbin
|
||||||
# doesn't seem to support filed field lists
|
# doesn't seem to support filed field lists
|
||||||
assert r.count(FILE_CONTENT) in [3, 4]
|
assert r.count(FILE_CONTENT) in [3, 4]
|
||||||
assert r.count('Content-Type: text/plain') == 2
|
assert r.count('Content-Type: text/plain') == 2
|
||||||
|
|
||||||
|
def test_upload_custom_content_type(self, httpbin):
|
||||||
|
r = http('--form', '--verbose', 'POST', httpbin.url + '/post',
|
||||||
|
f'test-file@{FILE_PATH_ARG};type=image/vnd.microsoft.icon')
|
||||||
|
assert HTTP_OK in r
|
||||||
|
# Content type is stripped from the filename
|
||||||
|
assert 'Content-Disposition: form-data; name="test-file";' \
|
||||||
|
f' filename="{os.path.basename(FILE_PATH)}"' in r
|
||||||
|
assert FILE_CONTENT in r
|
||||||
|
assert 'Content-Type: image/vnd.microsoft.icon' in r
|
||||||
|
|
||||||
|
|
||||||
class TestRequestBodyFromFilePath:
|
class TestRequestBodyFromFilePath:
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user