Skip to content

Commit

Permalink
Merge pull request #1417 from pallets/rfc2231-parameter-continuation
Browse files Browse the repository at this point in the history
Rfc2231 parameter continuation
  • Loading branch information
davidism committed Dec 9, 2018
2 parents 58732f1 + 7298c48 commit 2605b38
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ Unreleased
- ``testtools`` is removed. It did not offer significant benefit
over the default test client.

- :func:`~http.parse_options_header` understands :rfc:`2231` parameter
continuations. (`#1417`_)

.. _#4: https://github.com/pallets/werkzeug/issues/4
.. _`#209`: https://github.com/pallets/werkzeug/pull/209
.. _`#609`: https://github.com/pallets/werkzeug/pull/609
Expand Down Expand Up @@ -233,6 +236,7 @@ Unreleased
.. _#1412: https://github.com/pallets/werkzeug/pull/1412
.. _#1413: https://github.com/pallets/werkzeug/pull/1413
.. _#1416: https://github.com/pallets/werkzeug/pull/1416
.. _#1417: https://github.com/pallets/werkzeug/pull/1417


Version 0.14.1
Expand Down
19 changes: 19 additions & 0 deletions tests/test_formparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,25 @@ def stream_factory():
assert parser.stream_factory is stream_factory
assert parser.cls is dict

def test_file_rfc2231_filename_continuations(self):
data = (
b"--foo\r\n"
b"Content-Type: text/plain; charset=utf-8\r\n"
b'Content-Disposition: form-data; name=rfc2231;\r\n'
b" filename*0*=ascii''a%20b%20;\r\n"
b" filename*1*=c%20d%20;\r\n"
b' filename*2="e f.txt"\r\n\r\n'
b"file contents\r\n--foo--"
)
request = Request.from_values(
input_stream=BytesIO(data),
content_length=len(data),
content_type="multipart/form-data; boundary=foo",
method="POST"
)
assert request.files["rfc2231"].filename == "a b c d e f.txt"
assert request.files["rfc2231"].read() == b"file contents"


class TestInternalFunctions(object):

Expand Down
33 changes: 28 additions & 5 deletions werkzeug/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,21 @@
_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)')
_unsafe_header_chars = set('()<>@,;:\"/[]?={} \t')
_option_header_piece_re = re.compile(r'''
;\s*
;\s*,?\s* # newlines were replaced with commas
(?P<key>
"[^"\\]*(?:\\.[^"\\]*)*" # quoted string
|
[^\s;,=*]+ # token
)
(?:\*(?P<count>\d+))? # *1, optional continuation index
\s*
(?: # optionally followed by =value
(?: # equals sign, possibly with encoding
\*\s*=\s* # * indicates extended notation
(?P<encoding>[^\s]+?)
'(?P<language>[^\s]*?)'
(?: # optional encoding
(?P<encoding>[^\s]+?)
'(?P<language>[^\s]*?)'
)?
|
=\s* # basic notation
)
Expand Down Expand Up @@ -355,6 +358,9 @@ def parse_options_header(value, multiple=False):
a slightly different format. For these headers use the
:func:`parse_dict_header` function.
.. versionchanged:: 0.15
:rfc:`2231` parameter continuations are handled.
.. versionadded:: 0.5
:param value: the header to parse.
Expand All @@ -376,19 +382,36 @@ def parse_options_header(value, multiple=False):
options = {}
# Parse options
rest = match.group(2)
continued_encoding = None
while rest:
optmatch = _option_header_piece_re.match(rest)
if not optmatch:
break
option, encoding, _, option_value = optmatch.groups()
option, count, encoding, language, option_value = optmatch.groups()
# Continuations don't have to supply the encoding after the
# first line. If we're in a continuation, track the current
# encoding to use for subsequent lines. Reset it when the
# continuation ends.
if not count:
continued_encoding = None
else:
if not encoding:
encoding = continued_encoding
continued_encoding = encoding
option = unquote_header_value(option)
if option_value is not None:
option_value = unquote_header_value(
option_value,
option == 'filename')
if encoding is not None:
option_value = _unquote(option_value).decode(encoding)
options[option] = option_value
if count:
# Continuations append to the existing value. For
# simplicity, this ignores the possibility of
# out-of-order indices, which shouldn't happen anyway.
options[option] = options.get(option, "") + option_value
else:
options[option] = option_value
rest = rest[optmatch.end():]
result.append(options)
if multiple is False:
Expand Down

0 comments on commit 2605b38

Please sign in to comment.