Commit 8d253a84 by Gabriel

Add a "relaxed" variant that accepts one case of broken grammar.

Also add better error handling, and tests for strict and relaxed
behaviour.
parent 2ab6dcb7
......@@ -166,7 +166,7 @@ def ensure_charset(text, encoding):
return text
def parse_headers(content_disposition, location=None):
def parse_headers(content_disposition, location=None, relaxed=False):
"""Build a ContentDisposition from header values.
"""
......@@ -201,25 +201,34 @@ def parse_headers(content_disposition, location=None):
# of LWS.
assert is_lws_safe(content_disposition)
parsed = content_disposition_value.parse(content_disposition)
if relaxed:
parser = content_disposition_value_relaxed
else:
parser = content_disposition_value
try:
parsed = parser.parse(content_disposition)
except FullFirstMatchException:
return ContentDisposition(location=location)
return ContentDisposition(
disposition=parsed[0], assocs=parsed[1:], location=location)
def parse_httplib2_response(response):
def parse_httplib2_response(response, **kwargs):
"""Build a ContentDisposition from an httplib2 response.
"""
return parse_headers(
response.get('content-disposition'), response['content-location'])
response.get('content-disposition'),
response['content-location'], **kwargs)
def parse_requests_response(response):
def parse_requests_response(response, **kwargs):
"""Build a ContentDisposition from a requests (PyPI) response.
"""
return parse_headers(
response.headers.get('content-disposition'), response.url)
response.headers.get('content-disposition'), response.url, **kwargs)
def parse_ext_value(val):
......@@ -318,6 +327,12 @@ with DroppedSpace():
content_disposition_value = (
disposition_type & Star(Drop(';') & disposition_parm))
# Allows nonconformant final semicolon
# I've seen it in the wild, and browsers accept it
# http://greenbytes.de/tech/tc2231/#attwithasciifilenamenqs
content_disposition_value_relaxed = (
content_disposition_value & Optional(Drop(';')))
def is_token_char(ch):
# Must be ascii, and neither a control char nor a separator char
......
......@@ -48,6 +48,19 @@ def test_location_fallback():
).filename_unsafe == u'baré.py'
def test_strict():
# Trailing ; means the header is rejected
assert parse_headers('attachment;').disposition == 'inline'
assert parse_headers('attachment; key=val;').disposition == 'inline'
def test_relaxed():
assert parse_headers(
'attachment;', relaxed=True).disposition == 'attachment'
assert parse_headers(
'attachment; key=val;', relaxed=True).disposition == 'attachment'
def test_roundtrip():
def roundtrip(filename):
return parse_headers(build_header(filename)).filename_unsafe
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment