Commit b98dcd40 by Gabriel

Simplify the API.

parent fefcfd32
...@@ -2,8 +2,11 @@ ...@@ -2,8 +2,11 @@
"""Implements RFC 6266, the Content-Disposition HTTP header. """Implements RFC 6266, the Content-Disposition HTTP header.
ContentDisposition handles the receiver side, parse_headers (and variant parse_httplib2_response) handles the receiver side.
header_for_filename handles the sender side. It returns a ContentDisposition object with attributes like is_inline,
filename_unsafe, filename_sanitized.
build_header handles the sender side.
""" """
from lepl import * from lepl import *
...@@ -15,7 +18,12 @@ import os.path ...@@ -15,7 +18,12 @@ import os.path
import re import re
__all__ = ('ContentDisposition', 'header_for_filename', ) __all__ = (
'ContentDisposition',
'parse_headers',
'parse_httplib2_response',
'build_header',
)
LangTagged = namedtuple('LangTagged', 'string langtag') LangTagged = namedtuple('LangTagged', 'string langtag')
...@@ -33,8 +41,8 @@ class ContentDisposition(object): ...@@ -33,8 +41,8 @@ class ContentDisposition(object):
def __init__(self, disposition='inline', assocs=None, location=None): def __init__(self, disposition='inline', assocs=None, location=None):
"""This constructor is used internally after parsing the header. """This constructor is used internally after parsing the header.
Instances should generally be created from a factory class Instances should generally be created from a factory
method, such as from_headers and from_httplib2_response. function, such as parse_headers and parse_httplib2_response.
""" """
self.disposition = disposition self.disposition = disposition
...@@ -122,13 +130,13 @@ class ContentDisposition(object): ...@@ -122,13 +130,13 @@ class ContentDisposition(object):
return 'ContentDisposition(%r, %r, %r)' % ( return 'ContentDisposition(%r, %r, %r)' % (
self.disposition, self.assocs, self.location) self.disposition, self.assocs, self.location)
@classmethod
def from_headers(cls, content_disposition, location=None): def parse_headers(content_disposition, location=None):
"""Build a ContentDisposition from header values. """Build a ContentDisposition from header values.
""" """
if content_disposition is None: if content_disposition is None:
return cls(location=location) return ContentDisposition(location=location)
# Both alternatives seem valid. # Both alternatives seem valid.
if False: if False:
...@@ -144,6 +152,7 @@ class ContentDisposition(object): ...@@ -144,6 +152,7 @@ class ContentDisposition(object):
# in the filename parameter. But it does mean we occasionally # in the filename parameter. But it does mean we occasionally
# give less-than-certain values for some legacy senders. # give less-than-certain values for some legacy senders.
content_disposition = content_disposition.encode('iso-8859-1') content_disposition = content_disposition.encode('iso-8859-1')
# Check the caller already did LWS-folding (normally done # Check the caller already did LWS-folding (normally done
# when separating header names and values; RFC 2616 section 2.2 # when separating header names and values; RFC 2616 section 2.2
# says it should be done before interpretation at any rate). # says it should be done before interpretation at any rate).
...@@ -155,16 +164,17 @@ class ContentDisposition(object): ...@@ -155,16 +164,17 @@ class ContentDisposition(object):
# However http doesn't allow isolated CR and LF in headers outside # However http doesn't allow isolated CR and LF in headers outside
# of LWS. # of LWS.
assert is_lws_safe(content_disposition) assert is_lws_safe(content_disposition)
parsed = content_disposition_value.parse(content_disposition) parsed = content_disposition_value.parse(content_disposition)
return ContentDisposition( return ContentDisposition(
disposition=parsed[0], assocs=parsed[1:], location=location) disposition=parsed[0], assocs=parsed[1:], location=location)
@classmethod
def from_httplib2_response(cls, response): def parse_httplib2_response(response):
"""Build a ContentDisposition from an httplib2 response. """Build a ContentDisposition from an httplib2 response.
""" """
return cls.from_headers( return parse_headers(
response['content-disposition'], response['content-location']) response['content-disposition'], response['content-location'])
...@@ -298,7 +308,7 @@ def qd_quote(text): ...@@ -298,7 +308,7 @@ def qd_quote(text):
return text.replace('\\', '\\\\').replace('"', '\\"') return text.replace('\\', '\\\\').replace('"', '\\"')
def header_for_filename( def build_header(
filename, disposition='attachment', filename_compat=None filename, disposition='attachment', filename_compat=None
): ):
"""Generate a Content-Disposition header for a given filename. """Generate a Content-Disposition header for a given filename.
...@@ -357,17 +367,17 @@ def header_for_filename( ...@@ -357,17 +367,17 @@ def header_for_filename(
def test_parsing(): def test_parsing():
cdfh = ContentDisposition.from_headers assert parse_headers(None).disposition == 'inline'
assert ContentDisposition().disposition == 'inline' assert parse_headers('attachment').disposition == 'attachment'
assert cdfh('attachment').disposition == 'attachment' assert parse_headers('attachment; key=val').assocs['key'] == 'val'
assert cdfh('attachment; key=val').assocs['key'] == 'val' assert parse_headers(
assert cdfh('attachment; filename=simple').filename_unsafe == 'simple' 'attachment; filename=simple').filename_unsafe == 'simple'
# test ISO-8859-1 # test ISO-8859-1
fname = cdfh(u'attachment; filename="oyé"').filename_unsafe fname = parse_headers(u'attachment; filename="oyé"').filename_unsafe
assert fname == u'oyé', repr(fname) assert fname == u'oyé', repr(fname)
cd = cdfh( cd = parse_headers(
'attachment; filename="EURO rates";' 'attachment; filename="EURO rates";'
' filename*=utf-8\'\'%e2%82%ac%20rates') ' filename*=utf-8\'\'%e2%82%ac%20rates')
assert cd.filename_unsafe == u'€ rates' assert cd.filename_unsafe == u'€ rates'
...@@ -375,8 +385,7 @@ def test_parsing(): ...@@ -375,8 +385,7 @@ def test_parsing():
def test_roundtrip(): def test_roundtrip():
def roundtrip(filename): def roundtrip(filename):
return ContentDisposition.from_headers( return parse_headers(build_header(filename)).filename_unsafe
header_for_filename(filename)).filename_unsafe
def assert_roundtrip(filename): def assert_roundtrip(filename):
assert roundtrip(filename) == filename assert roundtrip(filename) == filename
......
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