diff --git a/docs/_newsfragments/1999.breakingchange.rst b/docs/_newsfragments/1999.breakingchange.rst new file mode 100644 index 000000000..6cac71257 --- /dev/null +++ b/docs/_newsfragments/1999.breakingchange.rst @@ -0,0 +1,6 @@ +The default value of the ``csv`` parameter in +:func:`~falcon.uri.parse_query_string` was changed to ``False``, matching the +default behavior of other parts of the framework (such as +:attr:`req.params `, the test client, etc). +If the old behavior fits your use case better, pass the ``csv=True`` keyword +argument explicitly. diff --git a/falcon/cyutil/uri.pyx b/falcon/cyutil/uri.pyx index da3b2b296..477e7cc7e 100644 --- a/falcon/cyutil/uri.pyx +++ b/falcon/cyutil/uri.pyx @@ -251,7 +251,7 @@ cdef cy_parse_query_string(unsigned char* data, Py_ssize_t length, def parse_query_string(unicode query_string not None, bint keep_blank=False, - bint csv=True): + bint csv=False): cdef bytes byte_string = query_string.encode('utf-8') cdef unsigned char* data = byte_string return cy_parse_query_string(data, len(byte_string), keep_blank, csv) diff --git a/falcon/util/uri.py b/falcon/util/uri.py index f4092634c..5daa7c68a 100644 --- a/falcon/util/uri.py +++ b/falcon/util/uri.py @@ -337,13 +337,13 @@ def decode(encoded_uri: str, unquote_plus: bool = True) -> str: def parse_query_string( - query_string: str, keep_blank: bool = False, csv: bool = True + query_string: str, keep_blank: bool = False, csv: bool = False ) -> Dict[str, Union[str, List[str]]]: """Parse a query string into a dict. Query string parameters are assumed to use standard form-encoding. Only parameters with values are returned. For example, given 'foo=bar&flag', - this function would ignore 'flag' unless the `keep_blank_qs_values` option + this function would ignore 'flag' unless the `keep_blank` option is set. Note: @@ -351,6 +351,8 @@ def parse_query_string( lists by repeating a given param multiple times, Falcon supports a more compact form in which the param may be given a single time but set to a ``list`` of comma-separated elements (e.g., 'foo=a,b,c'). + This comma-separated format can be enabled by setting the `csv` + option (see below) to ``True``. When using this format, all commas uri-encoded will not be treated by Falcon as a delimiter. If the client wants to send a value as a list, @@ -365,12 +367,13 @@ def parse_query_string( they do not have a value (default ``False``). For comma-separated values, this option also determines whether or not empty elements in the parsed list are retained. - csv: Set to ``False`` in order to disable splitting query - parameters on ``,`` (default ``True``). Depending on the user agent, - encoding lists as multiple occurrences of the same parameter might - be preferable. In this case, setting `parse_qs_csv` to ``False`` - will cause the framework to treat commas as literal characters in - each occurring parameter value. + csv: Set to ``True`` in order to enable splitting query + parameters on ``,`` (default ``False``). + Depending on the user agent, encoding lists as multiple occurrences + of the same parameter might be preferable. In this case, keeping + `parse_qs_csv` at its default value (``False``) will cause the + framework to treat commas as literal characters in each occurring + parameter value. Returns: dict: A dictionary of (*name*, *value*) pairs, one per query diff --git a/tests/test_utils.py b/tests/test_utils.py index 83a8dacce..349032856 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -380,13 +380,13 @@ def test_parse_query_string(self): result = uri.parse_query_string(query_string) assert result['a'] == decoded_url assert result['b'] == decoded_json - assert result['c'] == ['1', '2', '3'] + assert result['c'] == '1,2,3' assert result['d'] == 'test' - assert result['e'] == ['a', '&=,'] + assert result['e'] == 'a,,&=,' assert result['f'] == ['a', 'a=b'] assert result['é'] == 'a=b' - result = uri.parse_query_string(query_string, True) + result = uri.parse_query_string(query_string, True, True) assert result['a'] == decoded_url assert result['b'] == decoded_json assert result['c'] == ['1', '2', '3'] @@ -395,6 +395,15 @@ def test_parse_query_string(self): assert result['f'] == ['a', 'a=b'] assert result['é'] == 'a=b' + result = uri.parse_query_string(query_string, csv=True) + assert result['a'] == decoded_url + assert result['b'] == decoded_json + assert result['c'] == ['1', '2', '3'] + assert result['d'] == 'test' + assert result['e'] == ['a', '&=,'] + assert result['f'] == ['a', 'a=b'] + assert result['é'] == 'a=b' + @pytest.mark.parametrize( 'query_string,keep_blank,expected', [