Accept valid Accept headers in swob.

"Accept: application/xml; charset=UTF-8" is totally valid, and has an
implicit q (quality) value of 1.0, just the same as "Accept: text/xml"
does.

Also, you can say things like:
Accept: text/xml; charset=UTF-8; q=0.9; anglebrackets="are awesome"
with as many arbitrary extensions as you want.

See RFC 2616 sections 14.1 Accept and 2.2 Basic Rules for details.

Fixes bug 1202453.

Change-Id: I18e6d0ee3fd6f9d889275ee8335e711c729b7171
This commit is contained in:
Samuel Merritt 2013-07-18 16:33:01 -07:00
parent c9de9f2b8d
commit 91e7e876b5
2 changed files with 36 additions and 7 deletions

View File

@ -603,9 +603,17 @@ class Accept(object):
:param headerval: value of the header as a str
"""
token = r'[^()<>@,;:\"/\[\]?={}\x00-\x20\x7f]+' # RFC 2616 2.2
acc_pattern = re.compile(r'^\s*(' + token + r')/(' + token +
r')(;\s*q=([\d.]+))?\s*$')
# RFC 2616 section 2.2
token = r'[^()<>@,;:\"/\[\]?={}\x00-\x20\x7f]+'
qdtext = r'[^"]'
quoted_pair = r'(?:\\.)'
quoted_string = r'"(?:' + qdtext + r'|' + quoted_pair + r')*"'
extension = (r'(?:\s*;\s*(?:' + token + r")\s*=\s*" + r'(?:' + token +
r'|' + quoted_string + r'))')
acc = (r'^\s*(' + token + r')/(' + token +
r')(' + extension + r'*?\s*)$')
acc_pattern = re.compile(acc)
def __init__(self, headerval):
self.headerval = headerval
@ -618,8 +626,22 @@ class Accept(object):
type_parms = self.acc_pattern.findall(typ)
if not type_parms:
raise ValueError('Invalid accept header')
typ, subtype, parms, quality = type_parms[0]
quality = float(quality or '1.0')
typ, subtype, parms = type_parms[0]
parms = [p.strip() for p in parms.split(';') if p.strip()]
seen_q_already = False
quality = 1.0
for parm in parms:
name, value = parm.split('=')
name = name.strip()
value = value.strip()
if name == 'q':
if seen_q_already:
raise ValueError('Multiple "q" params')
seen_q_already = True
quality = float(value)
pattern = '^' + \
(self.token if typ == '*' else re.escape(typ)) + '/' + \
(self.token if subtype == '*' else re.escape(subtype)) + '$'

View File

@ -261,7 +261,10 @@ class TestAccept(unittest.TestCase):
def test_accept_xml(self):
for accept in ('application/xml', 'application/xml;q=1.0,*/*;q=0.9',
'*/*;q=0.9,application/xml;q=1.0'):
'*/*;q=0.9,application/xml;q=1.0',
'application/xml;charset=UTF-8',
'application/xml;charset=UTF-8;qws="quoted with space"',
'application/xml; q=0.99 ; qws="quoted with space"'):
acc = swift.common.swob.Accept(accept)
match = acc.best_match(['text/plain', 'application/xml',
'text/xml'])
@ -270,7 +273,10 @@ class TestAccept(unittest.TestCase):
def test_accept_invalid(self):
for accept in ('*', 'text/plain,,', 'some stuff',
'application/xml;q=1.0;q=1.1', 'text/plain,*',
'text /plain', 'text\x7f/plain'):
'text /plain', 'text\x7f/plain',
'text/plain;a=b=c',
'text/plain;q=1;q=2',
'text/plain; ubq="unbalanced " quotes"'):
acc = swift.common.swob.Accept(accept)
match = acc.best_match(['text/plain', 'application/xml',
'text/xml'])
@ -280,6 +286,7 @@ class TestAccept(unittest.TestCase):
acc = swift.common.swob.Accept("application/json")
self.assertEquals(repr(acc), "application/json")
class TestRequest(unittest.TestCase):
def test_blank(self):
req = swift.common.swob.Request.blank(