From 50747b93fddd7154a73f3899536e5ea3819e092b Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 7 Apr 2017 17:34:26 -0700 Subject: [PATCH] add test for positional header parameters pass args to _options_header_vkw allow positional args to set too more permissive mapping vs sequence check --- tests/test_datastructures.py | 3 +++ werkzeug/datastructures.py | 50 ++++++++++++++++++++++++------------ werkzeug/http.py | 9 +++---- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index 28bfa8b15e..558158bd28 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -631,6 +631,9 @@ def test_basic_interface(self): headers.add('x', 'y', z='"') assert headers['x'] == r'y; z="\""' + headers.add('multi', 'yes', ('a', '1',), ('b', '2'), c='3') + assert headers['multi'] == 'yes; a=1; b=2; c=3' + def test_defaults_and_conversion(self): # defaults headers = self.storage_class([ diff --git a/werkzeug/datastructures.py b/werkzeug/datastructures.py index d976db2f50..977803d097 100644 --- a/werkzeug/datastructures.py +++ b/werkzeug/datastructures.py @@ -889,9 +889,20 @@ def popitemlist(self): return key, [x.value for x in buckets] -def _options_header_vkw(value, kw): - return dump_options_header(value, dict((k.replace('_', '-'), v) - for k, v in kw.items())) +def _options_header_vkw(value, args, kwargs): + if args: + value = dump_options_header( + value, + ((k.replace('_', '-'), v) for k, v in args) + ) + + if kwargs: + value = dump_options_header( + value, + dict((k.replace('_', '-'), v) for k, v in iteritems(kwargs)) + ) + + return value def _unicodify_header_value(value): @@ -1140,7 +1151,7 @@ def __iter__(self): def __len__(self): return len(self._list) - def add(self, _key, _value, *args, **kw): + def add(self, _key, _value, *args, **kwargs): """Add a new header tuple to the list. Keyword arguments can specify additional parameters for the header @@ -1153,17 +1164,24 @@ def add(self, _key, _value, *args, **kw): The keyword argument dumping uses :func:`dump_options_header` behind the scenes. - In some cases, it would be needed to ensure the order of parameters. + In some cases the order of the additional parameters is important. For those cases, a list of ``(key, value)`` tuples can be passed in - *args. + ``*args``:: + + d.add( + 'Content-Disposition', 'attachment', + ('filename', 'foo.png'), ('filename*', "UTF-8''fo%C3%B6.png") + ) + + .. versionadded:: 0.13.0 + Key/value tuples as positional arguments are supported to ensure + the order of parameters. .. versionadded:: 0.4.1 keyword arguments were added for :mod:`wsgiref` compatibility. """ - if args: - _value = dump_options_header(_value, args) - if kw: - _value = _options_header_vkw(_value, kw) + if args or kwargs: + _value = _options_header_vkw(_value, args, kwargs) _value = _unicodify_header_value(_value) self._validate_value(_value) self._list.append((_key, _value)) @@ -1187,14 +1205,14 @@ def clear(self): """Clears all headers.""" del self._list[:] - def set(self, _key, _value, **kw): + def set(self, _key, _value, *args, **kwargs): """Remove all header tuples for `key` and add a new one. The newly added key either appears at the end of the list if there was no entry or replaces the first one. - Keyword arguments can specify additional parameters for the header - value, with underscores converted to dashes. See :meth:`add` for - more information. + Positional and keyword arguments can specify additional parameters for + the header value, with underscores converted to dashes. See + :meth:`add` for more information. .. versionchanged:: 0.6.1 :meth:`set` now accepts the same arguments as :meth:`add`. @@ -1202,8 +1220,8 @@ def set(self, _key, _value, **kw): :param key: The key to be inserted. :param value: The value to be inserted. """ - if kw: - _value = _options_header_vkw(_value, kw) + if args or kwargs: + _value = _options_header_vkw(_value, args, kwargs) _value = _unicodify_header_value(_value) self._validate_value(_value) if not self._list: diff --git a/werkzeug/http.py b/werkzeug/http.py index fa9e82b091..d3a7f22370 100644 --- a/werkzeug/http.py +++ b/werkzeug/http.py @@ -17,6 +17,7 @@ :license: BSD, see LICENSE for more details. """ import re +from collections import Mapping from time import time, gmtime try: from email.utils import parsedate_tz @@ -207,15 +208,13 @@ def dump_options_header(header, options): """The reverse function to :func:`parse_options_header`. :param header: the header to dump - :param options: a dict of options to append. + :param options: a dict of options to append. Or pass a sequence of + key, value pairs to ensure the order of the keys """ segments = [] if header is not None: segments.append(header) - if isinstance(options, tuple): - iter_func = iter - else: - iter_func = iteritems + iter_func = iteritems if isinstance(options, Mapping) else iter for key, value in iter_func(options): if value is None: segments.append(key)