'\' only escapes separator characters in req-items

It makes easier to work with Windows paths.

Closes #253, #254
This commit is contained in:
Jakub Roztocil 2014-09-05 18:33:46 +02:00
parent 1035710956
commit 5084f18568
3 changed files with 51 additions and 28 deletions

View File

@ -1277,6 +1277,9 @@ Changelog
* Added `CONTRIBUTING`_. * Added `CONTRIBUTING`_.
* Fixed ``User-Agent`` overwriting when used within a session. * Fixed ``User-Agent`` overwriting when used within a session.
* Fixed handling of empty passwords in URL credentials. * Fixed handling of empty passwords in URL credentials.
* To make it easier to deal with Windows paths in request items, ``\``
now only special characters (the ones that are used as key-value
separators).
* `0.8.0`_ (2014-01-25) * `0.8.0`_ (2014-01-25)
* Added ``field=@file.txt`` and ``field:=@file.json`` for embedding * Added ``field=@file.txt`` and ``field:=@file.json`` for embedding
the contents of text and JSON files into request data. the contents of text and JSON files into request data.

View File

@ -398,6 +398,9 @@ class KeyValue(object):
def __eq__(self, other): def __eq__(self, other):
return self.__dict__ == other.__dict__ return self.__dict__ == other.__dict__
def __repr__(self):
return repr(self.__dict__)
class SessionNameValidator(object): class SessionNameValidator(object):
@ -424,6 +427,9 @@ class KeyValueArgType(object):
def __init__(self, *separators): def __init__(self, *separators):
self.separators = separators self.separators = separators
self.special_characters = set('\\')
for separator in separators:
self.special_characters.update(separator)
def __call__(self, string): def __call__(self, string):
"""Parse `string` and return `self.key_value_class()` instance. """Parse `string` and return `self.key_value_class()` instance.
@ -446,15 +452,16 @@ class KeyValueArgType(object):
=> ['foo', Escaped('='), 'bar', Escaped('\\'), 'baz'] => ['foo', Escaped('='), 'bar', Escaped('\\'), 'baz']
""" """
backslash = '\\'
tokens = [''] tokens = ['']
esc = False s = iter(s)
for c in s: for c in s:
if esc: if c == backslash:
tokens.extend([Escaped(c), '']) nc = next(s, '')
esc = False if nc in self.special_characters:
tokens.extend([Escaped(nc), ''])
else: else:
if c == '\\': tokens[-1] += c + nc
esc = True
else: else:
tokens[-1] += c tokens[-1] += c
return tokens return tokens

View File

@ -18,26 +18,28 @@ from fixtures import (
class TestItemParsing: class TestItemParsing:
key_value_type = KeyValueArgType(*input.SEP_GROUP_ALL_ITEMS) key_value = KeyValueArgType(*input.SEP_GROUP_ALL_ITEMS)
def test_invalid_items(self): def test_invalid_items(self):
items = ['no-separator'] items = ['no-separator']
for item in items: for item in items:
pytest.raises(argparse.ArgumentTypeError, pytest.raises(argparse.ArgumentTypeError, self.key_value, item)
self.key_value_type, item)
def test_escape(self): def test_escape_separator(self):
items = input.parse_items([ items = input.parse_items([
# headers # headers
self.key_value_type(r'foo\:bar:baz'), self.key_value(r'foo\:bar:baz'),
self.key_value_type(r'jack\@jill:hill'), self.key_value(r'jack\@jill:hill'),
# data # data
self.key_value_type(r'baz\=bar=foo'), self.key_value(r'baz\=bar=foo'),
# files # files
self.key_value_type(r'bar\@baz@%s' % FILE_PATH_ARG) self.key_value(r'bar\@baz@%s' % FILE_PATH_ARG),
]) ])
# `requests.structures.CaseInsensitiveDict` => `dict` # `requests.structures.CaseInsensitiveDict` => `dict`
headers = dict(items.headers._store.values()) headers = dict(items.headers._store.values())
assert headers == { assert headers == {
'foo:bar': 'baz', 'foo:bar': 'baz',
'jack@jill': 'hill', 'jack@jill': 'hill',
@ -45,25 +47,36 @@ class TestItemParsing:
assert items.data == {'baz=bar': 'foo'} assert items.data == {'baz=bar': 'foo'}
assert 'bar@baz' in items.files assert 'bar@baz' in items.files
@pytest.mark.parametrize(('string', 'key', 'sep', 'value'), [
('path=c:\windows', 'path', '=', 'c:\windows'),
('path=c:\windows\\', 'path', '=', 'c:\windows\\'),
('path\==c:\windows', 'path=', '=', 'c:\windows'),
])
def test_backslash_before_non_special_character_does_not_escape(
self, string, key, sep, value):
expected = KeyValue(orig=string, key=key, sep=sep, value=value)
actual = self.key_value(string)
assert actual == expected
def test_escape_longsep(self): def test_escape_longsep(self):
items = input.parse_items([ items = input.parse_items([
self.key_value_type(r'bob\:==foo'), self.key_value(r'bob\:==foo'),
]) ])
assert items.params == {'bob:': 'foo'} assert items.params == {'bob:': 'foo'}
def test_valid_items(self): def test_valid_items(self):
items = input.parse_items([ items = input.parse_items([
self.key_value_type('string=value'), self.key_value('string=value'),
self.key_value_type('header:value'), self.key_value('header:value'),
self.key_value_type('list:=["a", 1, {}, false]'), self.key_value('list:=["a", 1, {}, false]'),
self.key_value_type('obj:={"a": "b"}'), self.key_value('obj:={"a": "b"}'),
self.key_value_type('eh:'), self.key_value('eh:'),
self.key_value_type('ed='), self.key_value('ed='),
self.key_value_type('bool:=true'), self.key_value('bool:=true'),
self.key_value_type('file@' + FILE_PATH_ARG), self.key_value('file@' + FILE_PATH_ARG),
self.key_value_type('query==value'), self.key_value('query==value'),
self.key_value_type('string-embed=@' + FILE_PATH_ARG), self.key_value('string-embed=@' + FILE_PATH_ARG),
self.key_value_type('raw-json-embed:=@' + JSON_FILE_PATH_ARG), self.key_value('raw-json-embed:=@' + JSON_FILE_PATH_ARG),
]) ])
# Parsed headers # Parsed headers