Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the package installable again #18

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions rfc6266.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# vim: set fileencoding=utf-8 sw=4 ts=4 et :

"""Implements RFC 6266, the Content-Disposition HTTP header.

parse_headers handles the receiver side.
Expand All @@ -13,8 +11,8 @@

from lepl import *
from collections import namedtuple
from urllib import quote, unquote
from urlparse import urlsplit
from urllib.parse import quote, unquote
from urllib.parse import urlsplit
from string import hexdigits, ascii_letters, digits

import logging
Expand Down Expand Up @@ -61,7 +59,7 @@ def percent_decode(string, **kwargs):
return unquote(string, **kwargs).decode(encoding)


class ContentDisposition(object):
class ContentDisposition:
"""
Records various indications and hints about content disposition.

Expand All @@ -83,7 +81,7 @@ def __init__(self, disposition='inline', assocs=None, location=None):
self.assocs = {}
else:
# XXX Check that parameters aren't repeated
self.assocs = dict((key.lower(), val) for (key, val) in assocs)
self.assocs = {key.lower(): val for (key, val) in assocs}

@property
def filename_unsafe(self):
Expand Down Expand Up @@ -160,7 +158,7 @@ def is_inline(self):
return self.disposition.lower() == 'inline'

def __repr__(self):
return 'ContentDisposition(%r, %r, %r)' % (
return 'ContentDisposition({!r}, {!r}, {!r})'.format(
self.disposition, self.assocs, self.location)


Expand Down Expand Up @@ -256,7 +254,7 @@ def parse_ext_value(val):
else:
charset, coded = val
langtag = None
if not PY3K and isinstance(coded, unicode):
if not PY3K and isinstance(coded, str):
coded = coded.encode('ascii')
decoded = percent_decode(coded, encoding=charset)
return LangTagged(decoded, langtag)
Expand All @@ -270,7 +268,7 @@ def CaseInsensitiveLiteral(lit):

# RFC 2616
separator_chars = "()<>@,;:\\\"/[]?={} \t"
ctl_chars = ''.join(chr(i) for i in xrange(32)) + chr(127)
ctl_chars = ''.join(chr(i) for i in range(32)) + chr(127)
nontoken_chars = separator_chars + ctl_chars

# RFC 5987
Expand Down Expand Up @@ -306,7 +304,7 @@ def CaseInsensitiveLiteral(lit):
# and all the others are defined with Any.
qdtext = AnyBut('"' + ctl_chars)

char = Any(''.join(chr(i) for i in xrange(128))) # ascii range: 0-127
char = Any(''.join(chr(i) for i in range(128))) # ascii range: 0-127

quoted_pair = Drop('\\') + char
quoted_string = Drop('"') & (quoted_pair | qdtext)[:, ...] & Drop('"')
Expand Down Expand Up @@ -428,26 +426,26 @@ def build_header(
rv = disposition

if is_token(filename):
rv += '; filename=%s' % (filename, )
rv += '; filename={}'.format(filename)
return rv
elif is_ascii(filename) and is_lws_safe(filename):
qd_filename = qd_quote(filename)
rv += '; filename="%s"' % (qd_filename, )
rv += '; filename="{}"'.format(qd_filename)
if qd_filename == filename:
# RFC 6266 claims some implementations are iffy on qdtext's
# backslash-escaping, we'll include filename* in that case.
return rv
elif filename_compat:
if is_token(filename_compat):
rv += '; filename=%s' % (filename_compat, )
rv += '; filename={}'.format(filename_compat)
else:
assert is_lws_safe(filename_compat)
rv += '; filename="%s"' % (qd_quote(filename_compat), )
rv += '; filename="{}"'.format(qd_quote(filename_compat))

# alnum are already considered always-safe, but the rest isn't.
# Python encodes ~ when it shouldn't, for example.
rv += "; filename*=utf-8''%s" % (percent_encode(
filename, safe=attr_chars_nonalnum, encoding='utf-8'), )
rv += "; filename*=utf-8''{}".format(percent_encode(
filename, safe=attr_chars_nonalnum, encoding='utf-8'))

# This will only encode filename_compat, if it used non-ascii iso-8859-1.
return rv.encode('iso-8859-1')
Expand Down
9 changes: 3 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from setuptools import setup

setup(
Expand All @@ -13,16 +12,14 @@
platforms='OS-independent',
py_modules=['rfc6266', 'test_rfc6266'],
install_requires=['LEPL'],
use_2to3=True,
long_description=open('README').read(),
classifiers=(
'Programming Language :: Python :: 2',
classifiers=[
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
'Topic :: Internet :: WWW/HTTP',
),
],
)

18 changes: 8 additions & 10 deletions test_rfc6266.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# vim: set fileencoding=utf-8 sw=4 ts=4 et :

from rfc6266 import (
parse_headers, parse_httplib2_response, parse_requests_response,
build_header)
Expand All @@ -14,13 +12,13 @@ def test_parsing():
'attachment; filename=simple').filename_unsafe == 'simple'

# test ISO-8859-1
fname = parse_headers(u'attachment; filename="oyé"').filename_unsafe
assert fname == u'oyé', repr(fname)
fname = parse_headers('attachment; filename="oyé"').filename_unsafe
assert fname == 'oyé', repr(fname)

cd = parse_headers(
'attachment; filename="EURO rates";'
' filename*=utf-8\'\'%e2%82%ac%20rates')
assert cd.filename_unsafe == u'€ rates'
assert cd.filename_unsafe == '€ rates'


@pytest.mark.skipif("(3,0) <= sys.version_info < (3,3)")
Expand All @@ -45,15 +43,15 @@ def test_requests(httpserver):
def test_location_fallback():
assert parse_headers(
None, location='https://foo/bar%c3%a9.py'
).filename_unsafe == u'baré.py'
).filename_unsafe == 'baré.py'

assert parse_headers(
None, location='https://foo/'
).filename_unsafe == u''
).filename_unsafe == ''

assert parse_headers(
None, location='https://foo/%C3%A9toil%C3%A9/'
).filename_unsafe == u'étoilé'
).filename_unsafe == 'étoilé'


def test_strict():
Expand All @@ -77,7 +75,7 @@ def test_relaxed():
cd = parse_headers(
'attachment; filename="spa ced";',
relaxed=True)
assert cd.filename_unsafe == u'spa ced'
assert cd.filename_unsafe == 'spa ced'



Expand All @@ -93,5 +91,5 @@ def assert_roundtrip(filename):
assert_roundtrip('a b ')
assert_roundtrip(' a b')
assert_roundtrip('a\"b')
assert_roundtrip(u'aéio o♥u"qfsdf!')
assert_roundtrip('aéio o♥u"qfsdf!')

3 changes: 1 addition & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist=py27,py26,py32,py33,pypy
envlist=py38,pypi

[testenv]
deps=
Expand All @@ -13,4 +13,3 @@ commands=py.test --pyargs test_rfc6266
# Changedir is a hack to prevent test discovery from finding the non-2to3
# source. We want tests to be import-based only.
changedir=.tox