Changes for Python 3: str/bytes fixes mostly.
This commit is contained in:
parent
9c12cd7b1a
commit
574b9b1088
|
@ -38,7 +38,7 @@ reference.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import urllib
|
import urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# faster C implementation
|
# faster C implementation
|
||||||
|
@ -103,7 +103,7 @@ def url_quote(value, fallback=None):
|
||||||
raise ValueError("value is None and no fallback supplied")
|
raise ValueError("value is None and no fallback supplied")
|
||||||
else:
|
else:
|
||||||
return fallback
|
return fallback
|
||||||
return urllib.quote(stringify(value))
|
return urllib.parse.quote(stringify(value))
|
||||||
|
|
||||||
_saved = None
|
_saved = None
|
||||||
def use_qpy():
|
def use_qpy():
|
||||||
|
@ -111,7 +111,7 @@ def use_qpy():
|
||||||
Switch to using 'qpy' as an alternative.
|
Switch to using 'qpy' as an alternative.
|
||||||
"""
|
"""
|
||||||
import qpy
|
import qpy
|
||||||
from qpy_templateio import qpy_TemplateIO
|
from .qpy_templateio import qpy_TemplateIO
|
||||||
|
|
||||||
global _saved, htmltext, stringify, htmlescape, TemplateIO
|
global _saved, htmltext, stringify, htmlescape, TemplateIO
|
||||||
if not _saved:
|
if not _saved:
|
||||||
|
|
|
@ -3,8 +3,10 @@ TemplateIO.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _escape_string(s):
|
def _escape_string(s):
|
||||||
if not isinstance(s, basestring):
|
if isinstance(s, bytes):
|
||||||
raise TypeError, 'string object required'
|
raise TypeError('escape_string no longer accepts bytes')
|
||||||
|
if not isinstance(s, str):
|
||||||
|
raise TypeError('string object required')
|
||||||
s = s.replace("&", "&")
|
s = s.replace("&", "&")
|
||||||
s = s.replace("<", "<")
|
s = s.replace("<", "<")
|
||||||
s = s.replace(">", ">")
|
s = s.replace(">", ">")
|
||||||
|
@ -16,17 +18,12 @@ def stringify(obj):
|
||||||
turning strings into unicode objects.
|
turning strings into unicode objects.
|
||||||
"""
|
"""
|
||||||
tp = type(obj)
|
tp = type(obj)
|
||||||
if issubclass(tp, basestring):
|
if isinstance(obj, (str, bytes)):
|
||||||
return obj
|
return obj
|
||||||
elif hasattr(tp, '__unicode__'):
|
|
||||||
s = tp.__unicode__(obj)
|
|
||||||
if not isinstance(s, basestring):
|
|
||||||
raise TypeError, '__unicode__ did not return a string'
|
|
||||||
return s
|
|
||||||
elif hasattr(tp, '__str__'):
|
elif hasattr(tp, '__str__'):
|
||||||
s = tp.__str__(obj)
|
s = tp.__str__(obj)
|
||||||
if not isinstance(s, basestring):
|
if not isinstance(s, str):
|
||||||
raise TypeError, '__str__ did not return a string'
|
raise TypeError('__str__ did not return a string')
|
||||||
return s
|
return s
|
||||||
else:
|
else:
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
@ -58,8 +55,11 @@ class htmltext(object):
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.s)
|
return len(self.s)
|
||||||
|
|
||||||
def __cmp__(self, other):
|
def __eq__(self, other):
|
||||||
return cmp(self.s, other)
|
return (self.s == other)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return (self.s < other)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.s)
|
return hash(self.s)
|
||||||
|
@ -156,17 +156,6 @@ class _QuoteWrapper(object):
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return _wraparg(self.value[key])
|
return _wraparg(self.value[key])
|
||||||
|
|
||||||
class _UnicodeWrapper(unicode):
|
|
||||||
|
|
||||||
__slots__ = ['raw']
|
|
||||||
|
|
||||||
def __new__(cls, s):
|
|
||||||
result = unicode.__new__(cls, _escape_string(s))
|
|
||||||
result.raw = s
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return _escape_string(`self.raw`)
|
|
||||||
|
|
||||||
|
|
||||||
def _wraparg(arg):
|
def _wraparg(arg):
|
||||||
|
@ -176,7 +165,7 @@ def _wraparg(arg):
|
||||||
return stringify(arg)
|
return stringify(arg)
|
||||||
elif isinstance(arg, str):
|
elif isinstance(arg, str):
|
||||||
# again, work around PyString_Format bug
|
# again, work around PyString_Format bug
|
||||||
return _UnicodeWrapper(arg)
|
return _escape_string(arg)
|
||||||
elif (isinstance(arg, int) or
|
elif (isinstance(arg, int) or
|
||||||
isinstance(arg, float)):
|
isinstance(arg, float)):
|
||||||
# ints, floats are okay
|
# ints, floats are okay
|
||||||
|
|
|
@ -5,10 +5,13 @@ from quixote.html import href, url_with_query, url_quote, nl2br
|
||||||
|
|
||||||
markupchars = '<>&"'
|
markupchars = '<>&"'
|
||||||
quotedchars = '<>&"'
|
quotedchars = '<>&"'
|
||||||
if sys.hexversion >= 0x20400a2:
|
unicodechars = '\\u1234'
|
||||||
unicodechars = u'\u1234'
|
|
||||||
else:
|
# Byte types...
|
||||||
unicodechars = 'x' # lie, Python <= 2.3 is broken
|
markupbytes = b'<>&"'
|
||||||
|
quotedbytes = b'<>&"'
|
||||||
|
bytebytes = b'\u1234'
|
||||||
|
|
||||||
|
|
||||||
class Wrapper:
|
class Wrapper:
|
||||||
def __init__(self, s):
|
def __init__(self, s):
|
||||||
|
@ -68,8 +71,8 @@ class HTMLTextTest (UTest):
|
||||||
def _check_init(self):
|
def _check_init(self):
|
||||||
assert str(htmltext('foo')) == 'foo'
|
assert str(htmltext('foo')) == 'foo'
|
||||||
assert str(htmltext(markupchars)) == markupchars
|
assert str(htmltext(markupchars)) == markupchars
|
||||||
assert unicode(htmltext(unicodechars)) == unicodechars
|
assert str(htmltext(unicodechars)) == unicodechars
|
||||||
assert str(htmltext(unicode(markupchars))) == markupchars
|
assert str(htmltext(str(markupchars))) == markupchars
|
||||||
assert str(htmltext(None)) == 'None'
|
assert str(htmltext(None)) == 'None'
|
||||||
assert str(htmltext(1)) == '1'
|
assert str(htmltext(1)) == '1'
|
||||||
try:
|
try:
|
||||||
|
@ -84,19 +87,31 @@ class HTMLTextTest (UTest):
|
||||||
assert stringify(Wrapper(markupchars)) is markupchars
|
assert stringify(Wrapper(markupchars)) is markupchars
|
||||||
assert stringify(Wrapper) == str(Wrapper)
|
assert stringify(Wrapper) == str(Wrapper)
|
||||||
assert stringify(None) == str(None)
|
assert stringify(None) == str(None)
|
||||||
|
assert stringify(markupbytes) is markupbytes
|
||||||
|
|
||||||
def check_escape(self):
|
def check_escape(self):
|
||||||
assert htmlescape(markupchars) == quotedchars
|
assert htmlescape(markupchars) == quotedchars
|
||||||
assert isinstance(htmlescape(markupchars), htmltext)
|
assert isinstance(htmlescape(markupchars), htmltext)
|
||||||
assert escape(markupchars) == quotedchars
|
assert escape(markupchars) == quotedchars
|
||||||
assert escape(unicodechars) == unicodechars
|
assert escape(unicodechars) == unicodechars
|
||||||
assert escape(unicode(markupchars)) == quotedchars
|
assert type(escape(markupchars)) == type(markupchars)
|
||||||
assert isinstance(escape(markupchars), basestring)
|
assert isinstance(escape(markupchars), str)
|
||||||
assert htmlescape(htmlescape(markupchars)) == quotedchars
|
assert htmlescape(htmlescape(markupchars)) == quotedchars
|
||||||
|
|
||||||
|
# Now try to pass bytes...
|
||||||
|
try:
|
||||||
|
escape(markupbytes)
|
||||||
|
assert 0
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ...and now not a string at all
|
||||||
try:
|
try:
|
||||||
escape(1)
|
escape(1)
|
||||||
assert 0
|
assert 0
|
||||||
except TypeError: pass
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def check_cmp(self):
|
def check_cmp(self):
|
||||||
s = htmltext("foo")
|
s = htmltext("foo")
|
||||||
|
@ -158,39 +173,27 @@ class HTMLTextTest (UTest):
|
||||||
except TypeError: pass
|
except TypeError: pass
|
||||||
|
|
||||||
def check_format(self):
|
def check_format(self):
|
||||||
s_fmt = htmltext('%s')
|
u_fmt = htmltext('%s')
|
||||||
u_fmt = htmltext(u'%s')
|
assert u_fmt % 'fooble' == "fooble"
|
||||||
assert s_fmt % 'foo' == "foo"
|
assert isinstance(u_fmt % 'wibblefoo', htmltext)
|
||||||
assert u_fmt % 'foo' == u"foo"
|
|
||||||
assert isinstance(s_fmt % 'foo', htmltext)
|
|
||||||
assert isinstance(u_fmt % 'foo', htmltext)
|
|
||||||
assert s_fmt % markupchars == quotedchars
|
|
||||||
assert u_fmt % markupchars == quotedchars
|
assert u_fmt % markupchars == quotedchars
|
||||||
assert s_fmt % None == "None"
|
|
||||||
assert u_fmt % None == "None"
|
assert u_fmt % None == "None"
|
||||||
assert s_fmt % unicodechars == unicodechars
|
|
||||||
assert u_fmt % unicodechars == unicodechars
|
assert u_fmt % unicodechars == unicodechars
|
||||||
assert s_fmt % htmltext(unicodechars) == unicodechars
|
|
||||||
assert u_fmt % htmltext(unicodechars) == unicodechars
|
assert u_fmt % htmltext(unicodechars) == unicodechars
|
||||||
assert htmltext('%r') % Wrapper(markupchars) == quotedchars
|
assert htmltext('%r') % Wrapper(markupchars) == quotedchars
|
||||||
assert htmltext('%r') % unicodechars == `unicodechars`
|
assert htmltext('%r') % unicodechars == repr(unicodechars)
|
||||||
assert htmltext('%s%s') % ('foo', htmltext(markupchars)) \
|
assert htmltext('%s%s') % ('foo', htmltext(markupchars)) \
|
||||||
== ("foo" + markupchars)
|
== ("foo" + markupchars)
|
||||||
assert htmltext('%d') % 10 == "10"
|
assert htmltext('%d') % 10 == "10"
|
||||||
assert htmltext('%.1f') % 10 == "10.0"
|
assert htmltext('%.1f') % 10 == "10.0"
|
||||||
try:
|
|
||||||
s_fmt % Broken()
|
|
||||||
assert 0
|
|
||||||
except BrokenError: pass
|
|
||||||
try:
|
try:
|
||||||
htmltext('%r') % Broken()
|
htmltext('%r') % Broken()
|
||||||
assert 0
|
assert 0
|
||||||
except BrokenError: pass
|
except BrokenError: pass
|
||||||
try:
|
|
||||||
s_fmt % (1, 2)
|
assert htmltext('%d') % 12300000000000000000 == "12300000000000000000"
|
||||||
assert 0
|
|
||||||
except TypeError: pass
|
|
||||||
assert htmltext('%d') % 12300000000000000000L == "12300000000000000000"
|
|
||||||
|
|
||||||
def check_dict_format(self):
|
def check_dict_format(self):
|
||||||
args = {'a': 'foo&', 'b': htmltext('bar&')}
|
args = {'a': 'foo&', 'b': htmltext('bar&')}
|
||||||
|
@ -232,8 +235,8 @@ class HTMLTextTest (UTest):
|
||||||
assert htmltext(' ').join([htmltext(markupchars), 'bar']) == \
|
assert htmltext(' ').join([htmltext(markupchars), 'bar']) == \
|
||||||
markupchars + " bar"
|
markupchars + " bar"
|
||||||
assert isinstance(htmltext('').join([]), htmltext)
|
assert isinstance(htmltext('').join([]), htmltext)
|
||||||
assert htmltext(u' ').join([unicodechars]) == unicodechars
|
assert htmltext(' ').join([unicodechars]) == unicodechars
|
||||||
assert htmltext(u' ').join(['']) == u''
|
assert htmltext(' ').join(['']) == ''
|
||||||
try:
|
try:
|
||||||
htmltext('').join(1)
|
htmltext('').join(1)
|
||||||
assert 0
|
assert 0
|
||||||
|
@ -307,8 +310,8 @@ class TemplateTest (UTest):
|
||||||
assert t.getvalue() == 'abcd'
|
assert t.getvalue() == 'abcd'
|
||||||
t += 123
|
t += 123
|
||||||
assert t.getvalue() == 'abcd123'
|
assert t.getvalue() == 'abcd123'
|
||||||
t += u'\u1234'
|
t += '\\u1234'
|
||||||
assert t.getvalue() == u'abcd123\u1234'
|
assert t.getvalue() == 'abcd123\\u1234'
|
||||||
try:
|
try:
|
||||||
t += Broken(); t.getvalue()
|
t += Broken(); t.getvalue()
|
||||||
assert 0
|
assert 0
|
||||||
|
|
|
@ -8,9 +8,9 @@ import re
|
||||||
import string
|
import string
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import urllib
|
import urllib.request, urllib.parse, urllib.error
|
||||||
import rfc822
|
import email
|
||||||
from StringIO import StringIO
|
import io
|
||||||
|
|
||||||
import quixote
|
import quixote
|
||||||
from quixote.http_response import HTTPResponse
|
from quixote.http_response import HTTPResponse
|
||||||
|
@ -48,10 +48,6 @@ def get_content_type(environ):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _decode_string(s, charset):
|
def _decode_string(s, charset):
|
||||||
if charset == 'iso-8859-1' == quixote.DEFAULT_CHARSET:
|
|
||||||
# To avoid breaking applications that are not Unicode-safe, return
|
|
||||||
# a str instance in this case.
|
|
||||||
return s
|
|
||||||
try:
|
try:
|
||||||
return s.decode(charset)
|
return s.decode(charset)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
|
@ -65,7 +61,9 @@ def parse_header(line):
|
||||||
Return the main content-type and a dictionary of options.
|
Return the main content-type and a dictionary of options.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
plist = map(lambda x: x.strip(), line.split(';'))
|
if isinstance(line, email.header.Header): # file upload
|
||||||
|
line = ''.join(val for val, charset in line._chunks)
|
||||||
|
plist = [val.strip() for val in line.split(';')]
|
||||||
key = plist.pop(0).lower()
|
key = plist.pop(0).lower()
|
||||||
pdict = {}
|
pdict = {}
|
||||||
for p in plist:
|
for p in plist:
|
||||||
|
@ -100,8 +98,8 @@ def parse_query(qs, charset):
|
||||||
value = ''
|
value = ''
|
||||||
else:
|
else:
|
||||||
name, value = chunk.split('=', 1)
|
name, value = chunk.split('=', 1)
|
||||||
name = urllib.unquote(name.replace('+', ' '))
|
name = urllib.parse.unquote_plus(name)
|
||||||
value = urllib.unquote(value.replace('+', ' '))
|
value = urllib.parse.unquote_plus(value)
|
||||||
name = _decode_string(name, charset)
|
name = _decode_string(name, charset)
|
||||||
value = _decode_string(value, charset)
|
value = _decode_string(value, charset)
|
||||||
_add_field_value(fields, name, value)
|
_add_field_value(fields, name, value)
|
||||||
|
@ -205,7 +203,7 @@ class HTTPRequest:
|
||||||
# middleware expect that. We cannot rely on the application to
|
# middleware expect that. We cannot rely on the application to
|
||||||
# read it completely (e.g. if there is some PublishError raised).
|
# read it completely (e.g. if there is some PublishError raised).
|
||||||
if length < 20000:
|
if length < 20000:
|
||||||
fp = StringIO()
|
fp = io.BytesIO()
|
||||||
else:
|
else:
|
||||||
fp = tempfile.TemporaryFile("w+b")
|
fp = tempfile.TemporaryFile("w+b")
|
||||||
remaining = length
|
remaining = length
|
||||||
|
@ -226,6 +224,8 @@ class HTTPRequest:
|
||||||
# Use the declared charset if it's provided (most browser's don't
|
# Use the declared charset if it's provided (most browser's don't
|
||||||
# provide it to avoid breaking old HTTP servers).
|
# provide it to avoid breaking old HTTP servers).
|
||||||
charset = params.get('charset', self.charset)
|
charset = params.get('charset', self.charset)
|
||||||
|
# should contain only ASCII characters but parse as iso-8859-1
|
||||||
|
query = query.decode('iso-8859-1')
|
||||||
self.form.update(parse_query(query, charset))
|
self.form.update(parse_query(query, charset))
|
||||||
|
|
||||||
def _process_multipart(self, length, params):
|
def _process_multipart(self, length, params):
|
||||||
|
@ -243,14 +243,14 @@ class HTTPRequest:
|
||||||
raise RequestError('unexpected end of multipart/form-data')
|
raise RequestError('unexpected end of multipart/form-data')
|
||||||
|
|
||||||
def _process_multipart_body(self, mimeinput, charset):
|
def _process_multipart_body(self, mimeinput, charset):
|
||||||
headers = StringIO()
|
headers = io.BytesIO()
|
||||||
lines = mimeinput.readpart()
|
lines = mimeinput.readpart()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
headers.write(line)
|
headers.write(line)
|
||||||
if line == '\r\n':
|
if line == b'\r\n':
|
||||||
break
|
break
|
||||||
headers.seek(0)
|
headers.seek(0)
|
||||||
headers = rfc822.Message(headers)
|
headers = email.message_from_binary_file(headers)
|
||||||
ctype, ctype_params = parse_header(headers.get('content-type', ''))
|
ctype, ctype_params = parse_header(headers.get('content-type', ''))
|
||||||
if ctype and 'charset' in ctype_params:
|
if ctype and 'charset' in ctype_params:
|
||||||
charset = ctype_params['charset']
|
charset = ctype_params['charset']
|
||||||
|
@ -272,7 +272,7 @@ class HTTPRequest:
|
||||||
upload.receive(lines)
|
upload.receive(lines)
|
||||||
_add_field_value(self.form, name, upload)
|
_add_field_value(self.form, name, upload)
|
||||||
else:
|
else:
|
||||||
value = _decode_string(''.join(lines), charset or self.charset)
|
value = _decode_string(b''.join(lines), charset or self.charset)
|
||||||
_add_field_value(self.form, name, value)
|
_add_field_value(self.form, name, value)
|
||||||
|
|
||||||
def get_header(self, name, default=None):
|
def get_header(self, name, default=None):
|
||||||
|
@ -408,7 +408,7 @@ class HTTPRequest:
|
||||||
any).
|
any).
|
||||||
"""
|
"""
|
||||||
return "%s://%s%s" % (self.get_scheme(), self.get_server(),
|
return "%s://%s%s" % (self.get_scheme(), self.get_server(),
|
||||||
urllib.quote(self.get_path(n)))
|
urllib.parse.quote(self.get_path(n)))
|
||||||
|
|
||||||
def get_environ(self, key, default=None):
|
def get_environ(self, key, default=None):
|
||||||
"""get_environ(key : string) -> string
|
"""get_environ(key : string) -> string
|
||||||
|
@ -616,7 +616,7 @@ def parse_cookies(text):
|
||||||
result[name] = value
|
result[name] = value
|
||||||
return result
|
return result
|
||||||
|
|
||||||
SAFE_CHARS = string.letters + string.digits + "-@&+=_., "
|
SAFE_CHARS = string.ascii_letters + string.digits + "-@&+=_., "
|
||||||
_safe_trans = None
|
_safe_trans = None
|
||||||
|
|
||||||
def make_safe_filename(s):
|
def make_safe_filename(s):
|
||||||
|
@ -737,7 +737,7 @@ class LineInput:
|
||||||
def __init__(self, fp, length):
|
def __init__(self, fp, length):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.length = length
|
self.length = length
|
||||||
self.buf = ''
|
self.buf = b''
|
||||||
|
|
||||||
def readline(self, maxlength=4096):
|
def readline(self, maxlength=4096):
|
||||||
# fill buffer
|
# fill buffer
|
||||||
|
@ -751,17 +751,17 @@ class LineInput:
|
||||||
self.buf += chunk
|
self.buf += chunk
|
||||||
# split into lines
|
# split into lines
|
||||||
buf = self.buf
|
buf = self.buf
|
||||||
i = buf.find('\r\n')
|
i = buf.find(b'\r\n')
|
||||||
if i >= 0:
|
if i >= 0:
|
||||||
i += 2
|
i += 2
|
||||||
self.buf = buf[i:]
|
self.buf = buf[i:]
|
||||||
return buf[:i]
|
return buf[:i]
|
||||||
elif buf.endswith('\r'):
|
elif buf.endswith(b'\r'):
|
||||||
# avoid splitting CR LF pairs
|
# avoid splitting CR LF pairs
|
||||||
self.buf = '\r'
|
self.buf = b'\r'
|
||||||
return buf[:-1]
|
return buf[:-1]
|
||||||
else:
|
else:
|
||||||
self.buf = ''
|
self.buf = b''
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
class MIMEInput:
|
class MIMEInput:
|
||||||
|
@ -772,7 +772,7 @@ class MIMEInput:
|
||||||
|
|
||||||
def __init__(self, fp, boundary, length):
|
def __init__(self, fp, boundary, length):
|
||||||
self.lineinput = LineInput(fp, length)
|
self.lineinput = LineInput(fp, length)
|
||||||
self.pat = re.compile(r'--%s(--)?' % re.escape(boundary))
|
self.pat = b'--' + boundary.encode('iso-8859-1')
|
||||||
self.done = False
|
self.done = False
|
||||||
|
|
||||||
def moreparts(self):
|
def moreparts(self):
|
||||||
|
@ -783,20 +783,20 @@ class MIMEInput:
|
||||||
"""Generate all the lines up to a MIME boundary. Note that you
|
"""Generate all the lines up to a MIME boundary. Note that you
|
||||||
must exhaust the generator before calling this function again."""
|
must exhaust the generator before calling this function again."""
|
||||||
assert not self.done
|
assert not self.done
|
||||||
last_line = ''
|
last_line = b''
|
||||||
while 1:
|
while 1:
|
||||||
line = self.lineinput.readline()
|
line = self.lineinput.readline()
|
||||||
if not line:
|
if not line:
|
||||||
# Hit EOF -- nothing more to read. This should *not* happen
|
# Hit EOF -- nothing more to read. This should *not* happen
|
||||||
# in a well-formed MIME message.
|
# in a well-formed MIME message.
|
||||||
raise EOFError('MIME boundary not found (end of input)')
|
raise EOFError('MIME boundary not found (end of input)')
|
||||||
if last_line.endswith('\r\n') or last_line == '':
|
# FIXME: check this
|
||||||
m = self.pat.match(line)
|
if last_line.endswith(b'\r\n') or last_line == b'':
|
||||||
if m:
|
if line.startswith(self.pat):
|
||||||
# If we hit the boundary line, return now. Forget
|
# If we hit the boundary line, return now. Forget
|
||||||
# the current line *and* the CRLF ending of the
|
# the current line *and* the CRLF ending of the
|
||||||
# previous line.
|
# previous line.
|
||||||
if m.group(1):
|
if line.startswith(self.pat + b'--'):
|
||||||
# hit final boundary
|
# hit final boundary
|
||||||
self.done = True
|
self.done = True
|
||||||
yield last_line[:-2]
|
yield last_line[:-2]
|
||||||
|
|
|
@ -249,12 +249,12 @@ class HTTPResponse:
|
||||||
return chunk
|
return chunk
|
||||||
|
|
||||||
def _compress_body(self, body):
|
def _compress_body(self, body):
|
||||||
"""(body: str) -> str
|
"""(body: bytes) -> bytes
|
||||||
"""
|
"""
|
||||||
n = len(body)
|
n = len(body)
|
||||||
compressed_body = ''.join(self._generate_compressed([body]))
|
compressed_body = b''.join(self._generate_compressed([body]))
|
||||||
ratio = float(n) / len(compressed_body)
|
ratio = float(n) / len(compressed_body)
|
||||||
#print "gzip original size %d, ratio %.1f" % (n, ratio)
|
#print("gzip original size %d, ratio %.1f" % (n, ratio))
|
||||||
if ratio > 1.0:
|
if ratio > 1.0:
|
||||||
# only compress if we save space
|
# only compress if we save space
|
||||||
self.set_header("Content-Encoding", "gzip")
|
self.set_header("Content-Encoding", "gzip")
|
||||||
|
@ -509,8 +509,8 @@ class HTTPResponse:
|
||||||
# The stream is terminated by a zero length chunk.
|
# The stream is terminated by a zero length chunk.
|
||||||
for chunk in stream:
|
for chunk in stream:
|
||||||
if chunk:
|
if chunk:
|
||||||
yield ''.join(['%x\r\n' % len(chunk), chunk, '\r\n'])
|
yield b''.join([('%x\r\n' % len(chunk)).encode(), chunk, b'\r\n'])
|
||||||
yield '0\r\n\r\n'
|
yield b'0\r\n\r\n'
|
||||||
|
|
||||||
def generate_body_chunks(self):
|
def generate_body_chunks(self):
|
||||||
stream = self._generate_encoded_body()
|
stream = self._generate_encoded_body()
|
||||||
|
@ -534,11 +534,13 @@ class HTTPResponse:
|
||||||
flush_output = not self.buffered and hasattr(output, 'flush')
|
flush_output = not self.buffered and hasattr(output, 'flush')
|
||||||
if include_status:
|
if include_status:
|
||||||
# "Status" header must come first.
|
# "Status" header must come first.
|
||||||
output.write("Status: %03d %s\r\n" % (self.status_code,
|
s = 'Status: %03d %s\r\n' % (self.status_code, self.reason_phrase)
|
||||||
self.reason_phrase))
|
output.write(s.encode('utf-8'))
|
||||||
|
|
||||||
for name, value in self.generate_headers():
|
for name, value in self.generate_headers():
|
||||||
output.write("%s: %s\r\n" % (name, value))
|
s = "%s: %s\r\n" % (name, value))
|
||||||
output.write("\r\n")
|
output.write(s.encode('utf-8'))
|
||||||
|
output.write(b"\r\n")
|
||||||
if flush_output:
|
if flush_output:
|
||||||
output.flush()
|
output.flush()
|
||||||
if not include_body:
|
if not include_body:
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
"""Logic for publishing modules and objects on the Web.
|
"""Logic for publishing modules and objects on the Web.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, traceback, StringIO
|
import sys, traceback, io
|
||||||
import time
|
import time
|
||||||
import urlparse
|
import urllib.parse
|
||||||
import cgitb
|
import cgitb
|
||||||
|
|
||||||
from quixote.errors import PublishError, MethodNotAllowedError, \
|
from quixote.errors import PublishError, MethodNotAllowedError, \
|
||||||
|
@ -186,7 +186,7 @@ class Publisher:
|
||||||
|
|
||||||
def _generate_plaintext_error(self, request, original_response,
|
def _generate_plaintext_error(self, request, original_response,
|
||||||
exc_type, exc_value, tb):
|
exc_type, exc_value, tb):
|
||||||
error_file = StringIO.StringIO()
|
error_file = io.StringIO()
|
||||||
|
|
||||||
# format the traceback
|
# format the traceback
|
||||||
traceback.print_exception(exc_type, exc_value, tb, file=error_file)
|
traceback.print_exception(exc_type, exc_value, tb, file=error_file)
|
||||||
|
@ -201,14 +201,23 @@ class Publisher:
|
||||||
|
|
||||||
def _generate_cgitb_error(self, request, original_response,
|
def _generate_cgitb_error(self, request, original_response,
|
||||||
exc_type, exc_value, tb):
|
exc_type, exc_value, tb):
|
||||||
error_file = StringIO.StringIO()
|
# let cgitb.Hook have the type it wants...
|
||||||
|
error_file = io.StringIO()
|
||||||
hook = cgitb.Hook(file=error_file)
|
hook = cgitb.Hook(file=error_file)
|
||||||
hook(exc_type, exc_value, tb)
|
hook(exc_type, exc_value, tb)
|
||||||
error_file.write('<h2>Original Request</h2>')
|
|
||||||
error_file.write(str(util.dump_request(request)))
|
byte_error_file = io.BytesIO()
|
||||||
error_file.write('<h2>Original Response</h2><pre>')
|
byte_error_file.write(b'<h2>Original Request</h2>')
|
||||||
original_response.write(error_file)
|
# dump_request returns an HTMLText object
|
||||||
error_file.write('</pre>')
|
s = str(util.dump_request(request))
|
||||||
|
byte_error_file.write(s.encode('latin-1', 'strict'))
|
||||||
|
byte_error_file.write(b'<h2>Original Response</h2><pre>')
|
||||||
|
original_response.write(byte_error_file)
|
||||||
|
byte_error_file.write(b'</pre>')
|
||||||
|
# Now we push the bytes to the "real" error file...
|
||||||
|
s = byte_error_file.getvalue().decode('latin-1')
|
||||||
|
error_file.write(s)
|
||||||
|
|
||||||
return error_file.getvalue()
|
return error_file.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
@ -332,7 +341,7 @@ def redirect(location, permanent=False):
|
||||||
not honor the redirect).
|
not honor the redirect).
|
||||||
"""
|
"""
|
||||||
request = _publisher.get_request()
|
request = _publisher.get_request()
|
||||||
location = urlparse.urljoin(request.get_url(), str(location))
|
location = urllib.parse.urljoin(request.get_url(), str(location))
|
||||||
return request.response.redirect(location, permanent)
|
return request.response.redirect(location, permanent)
|
||||||
|
|
||||||
def get_session():
|
def get_session():
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
"""An HTTP handler for Medusa that publishes a Quixote application.
|
"""An HTTP handler for Medusa that publishes a Quixote application.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncore, rfc822, socket, urllib
|
import asyncore
|
||||||
from StringIO import StringIO
|
import email
|
||||||
|
import socket, urllib.request, urllib.parse, urllib.error
|
||||||
|
from io import StringIO
|
||||||
from medusa import http_server, xmlrpc_handler
|
from medusa import http_server, xmlrpc_handler
|
||||||
import quixote
|
import quixote
|
||||||
|
|
||||||
|
@ -14,7 +16,7 @@ class StreamProducer:
|
||||||
|
|
||||||
def more(self):
|
def more(self):
|
||||||
try:
|
try:
|
||||||
return self.chunks.next()
|
return next(self.chunks)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@ -29,7 +31,7 @@ class QuixoteHandler:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def handle_request(self, request):
|
def handle_request(self, request):
|
||||||
msg = rfc822.Message(StringIO('\n'.join(request.header)))
|
msg = email.Message(StringIO('\n'.join(request.header)))
|
||||||
length = int(msg.get('Content-Length', '0'))
|
length = int(msg.get('Content-Length', '0'))
|
||||||
if length:
|
if length:
|
||||||
request.collector = xmlrpc_handler.collector(self, request)
|
request.collector = xmlrpc_handler.collector(self, request)
|
||||||
|
@ -37,7 +39,7 @@ class QuixoteHandler:
|
||||||
self.continue_request('', request)
|
self.continue_request('', request)
|
||||||
|
|
||||||
def continue_request(self, data, request):
|
def continue_request(self, data, request):
|
||||||
msg = rfc822.Message(StringIO('\n'.join(request.header)))
|
msg = email.Message(StringIO('\n'.join(request.header)))
|
||||||
remote_addr, remote_port = request.channel.addr
|
remote_addr, remote_port = request.channel.addr
|
||||||
if '#' in request.uri:
|
if '#' in request.uri:
|
||||||
# MSIE is buggy and sometimes includes fragments in URLs
|
# MSIE is buggy and sometimes includes fragments in URLs
|
||||||
|
@ -48,7 +50,7 @@ class QuixoteHandler:
|
||||||
path = request.uri
|
path = request.uri
|
||||||
query_string = ''
|
query_string = ''
|
||||||
|
|
||||||
path = urllib.unquote(path)
|
path = urllib.parse.unquote(path)
|
||||||
server_port = str(self.server.port)
|
server_port = str(self.server.port)
|
||||||
http_host = msg.get("Host")
|
http_host = msg.get("Host")
|
||||||
if http_host:
|
if http_host:
|
||||||
|
|
|
@ -11,8 +11,8 @@ class QuixoteHandler(scgi_server.SCGIHandler):
|
||||||
self.script_name = script_name
|
self.script_name = script_name
|
||||||
|
|
||||||
def handle_connection(self, conn):
|
def handle_connection(self, conn):
|
||||||
input = conn.makefile("r")
|
input = conn.makefile("rb")
|
||||||
output = conn.makefile("w")
|
output = conn.makefile("wb")
|
||||||
env = self.read_env(input)
|
env = self.read_env(input)
|
||||||
|
|
||||||
if self.script_name is not None:
|
if self.script_name is not None:
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
"""A simple, single threaded, synchronous HTTP server.
|
"""A simple, single threaded, synchronous HTTP server.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
import urllib
|
import urllib.request, urllib.parse, urllib.error
|
||||||
import quixote
|
import quixote
|
||||||
from quixote import get_publisher
|
from quixote import get_publisher
|
||||||
from quixote.util import import_object
|
from quixote.util import import_object
|
||||||
|
@ -28,12 +28,9 @@ class HTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
env['PATH_INFO'], env['QUERY_STRING'] = self.path.split('?', 1)
|
env['PATH_INFO'], env['QUERY_STRING'] = self.path.split('?', 1)
|
||||||
else:
|
else:
|
||||||
env['PATH_INFO'] = self.path
|
env['PATH_INFO'] = self.path
|
||||||
env['PATH_INFO'] = urllib.unquote(env['PATH_INFO'])
|
env['PATH_INFO'] = urllib.parse.unquote(env['PATH_INFO'])
|
||||||
if self.headers.typeheader is None:
|
env['CONTENT_TYPE'] = self.headers.get('content-type')
|
||||||
env['CONTENT_TYPE'] = self.headers.type
|
env['CONTENT_LENGTH'] = self.headers.get('content-length') or "0"
|
||||||
else:
|
|
||||||
env['CONTENT_TYPE'] = self.headers.typeheader
|
|
||||||
env['CONTENT_LENGTH'] = self.headers.getheader('content-length') or "0"
|
|
||||||
for name, value in self.headers.items():
|
for name, value in self.headers.items():
|
||||||
header_name = 'HTTP_' + name.upper().replace('-', '_')
|
header_name = 'HTTP_' + name.upper().replace('-', '_')
|
||||||
env[header_name] = value
|
env[header_name] = value
|
||||||
|
@ -56,12 +53,11 @@ class HTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
# single threaded server, persistent connections will block others
|
# single threaded server, persistent connections will block others
|
||||||
response.set_header('connection', 'close')
|
response.set_header('connection', 'close')
|
||||||
try:
|
try:
|
||||||
self.send_response(response.get_status_code(),
|
self.send_response(response.get_status_code(), response.get_reason_phrase())
|
||||||
response.get_reason_phrase())
|
self.flush_headers()
|
||||||
response.write(self.wfile, include_status=False,
|
response.write(self.wfile, include_status=False, include_body=include_body)
|
||||||
include_body=include_body)
|
except IOError as err:
|
||||||
except IOError, err:
|
print("IOError while sending response ignored: %s" % err)
|
||||||
print "IOError while sending response ignored: %s" % err
|
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
return self.process(self.get_cgi_env('POST'))
|
return self.process(self.get_cgi_env('POST'))
|
||||||
|
@ -78,14 +74,7 @@ class HTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
that adds the 'Date' header is removed to avoid duplicating the one
|
that adds the 'Date' header is removed to avoid duplicating the one
|
||||||
that Quixote adds and the log_request() call has been removed.
|
that Quixote adds and the log_request() call has been removed.
|
||||||
"""
|
"""
|
||||||
if message is None:
|
self.send_response_only(code, message)
|
||||||
if code in self.responses:
|
|
||||||
message = self.responses[code][0]
|
|
||||||
else:
|
|
||||||
message = ''
|
|
||||||
if self.request_version != 'HTTP/0.9':
|
|
||||||
self.wfile.write("%s %d %s\r\n" %
|
|
||||||
(self.protocol_version, code, message))
|
|
||||||
self.send_header('Server', self.version_string())
|
self.send_header('Server', self.version_string())
|
||||||
|
|
||||||
def run(create_publisher, host='', port=80, https=False):
|
def run(create_publisher, host='', port=80, https=False):
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
"""An HTTP server for Twisted that publishes a Quixote application.
|
"""An HTTP server for Twisted that publishes a Quixote application.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import urllib
|
import urllib.request, urllib.parse, urllib.error
|
||||||
from twisted.web import http, server
|
from twisted.protocols import http
|
||||||
|
from twisted.web import server
|
||||||
from twisted.python import threadable
|
from twisted.python import threadable
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ class QuixoteRequest(server.Request):
|
||||||
"""
|
"""
|
||||||
# Twisted doesn't decode the path for us, so let's do it here.
|
# Twisted doesn't decode the path for us, so let's do it here.
|
||||||
if '%' in self.path:
|
if '%' in self.path:
|
||||||
self.path = urllib.unquote(self.path)
|
self.path = urllib.parse.unquote(self.path)
|
||||||
|
|
||||||
serverName = self.getRequestHostname().split(':')[0]
|
serverName = self.getRequestHostname().split(':')[0]
|
||||||
env = {"SERVER_SOFTWARE": server.version,
|
env = {"SERVER_SOFTWARE": server.version,
|
||||||
|
@ -109,7 +110,7 @@ class QuixoteProducer:
|
||||||
def resumeProducing(self):
|
def resumeProducing(self):
|
||||||
if self.request:
|
if self.request:
|
||||||
try:
|
try:
|
||||||
chunk = self.stream.next()
|
chunk = next(self.stream)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
self.request.unregisterProducer()
|
self.request.unregisterProducer()
|
||||||
self.request.finish()
|
self.request.finish()
|
||||||
|
|
|
@ -18,9 +18,10 @@ import os
|
||||||
import time
|
import time
|
||||||
import base64
|
import base64
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import urllib
|
import urllib.request, urllib.parse, urllib.error
|
||||||
import xmlrpclib
|
import xmlrpc.client
|
||||||
from rfc822 import formatdate
|
from email.utils import formatdate
|
||||||
|
|
||||||
import quixote
|
import quixote
|
||||||
from quixote import errors
|
from quixote import errors
|
||||||
from quixote.directory import Directory
|
from quixote.directory import Directory
|
||||||
|
@ -68,20 +69,20 @@ def xmlrpc(request, func):
|
||||||
data = request.stdin.read(length)
|
data = request.stdin.read(length)
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
params, method = xmlrpclib.loads(data)
|
params, method = xmlrpc.client.loads(data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = func(method, params)
|
result = func(method, params)
|
||||||
except xmlrpclib.Fault, exc:
|
except xmlrpc.client.Fault as exc:
|
||||||
result = exc
|
result = exc
|
||||||
except:
|
except:
|
||||||
# report exception back to client
|
# report exception back to client
|
||||||
result = xmlrpclib.dumps(
|
result = xmlrpc.client.dumps(
|
||||||
xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
|
xmlrpc.client.Fault(1, "%s:%s" % (sys.exc_info()[0], sys.exc_info()[1]))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = (result,)
|
result = (result,)
|
||||||
result = xmlrpclib.dumps(result, methodresponse=1)
|
result = xmlrpc.client.dumps(result, methodresponse=1)
|
||||||
|
|
||||||
request.response.set_content_type('text/xml')
|
request.response.set_content_type('text/xml')
|
||||||
return result
|
return result
|
||||||
|
@ -98,7 +99,7 @@ class FileStream(Stream):
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def next(self):
|
def __next__(self):
|
||||||
chunk = self.fp.read(self.CHUNK_SIZE)
|
chunk = self.fp.read(self.CHUNK_SIZE)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
@ -249,7 +250,7 @@ class StaticDirectory(Directory):
|
||||||
for filename in files:
|
for filename in files:
|
||||||
filepath = os.path.join(self.path, filename)
|
filepath = os.path.join(self.path, filename)
|
||||||
marker = os.path.isdir(filepath) and "/" or ""
|
marker = os.path.isdir(filepath) and "/" or ""
|
||||||
r += template % (urllib.quote(filename), filename, marker)
|
r += template % (urllib.parse.quote(filename), filename, marker)
|
||||||
r += htmltext('</pre>')
|
r += htmltext('</pre>')
|
||||||
body = r.getvalue()
|
body = r.getvalue()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -9,10 +9,8 @@ To create an application object, execute
|
||||||
Authors: Mike Orr <mso@oz.net> and Titus Brown <titus@caltech.edu>.
|
Authors: Mike Orr <mso@oz.net> and Titus Brown <titus@caltech.edu>.
|
||||||
Last updated 2005-05-03.
|
Last updated 2005-05-03.
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
|
|
||||||
from http_request import HTTPRequest
|
from .http_request import HTTPRequest
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
###### QWIP: WSGI COMPATIBILITY WRAPPER FOR QUIXOTE #####################
|
###### QWIP: WSGI COMPATIBILITY WRAPPER FOR QUIXOTE #####################
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,9 @@ import sys, subprocess
|
||||||
|
|
||||||
import quixote
|
import quixote
|
||||||
from quixote.server.simple_server import run
|
from quixote.server.simple_server import run
|
||||||
from StringIO import StringIO
|
from io import StringIO
|
||||||
import os
|
import os
|
||||||
import socket
|
import urllib.request, urllib.parse, urllib.error
|
||||||
import urllib
|
|
||||||
|
|
||||||
_server_url = None
|
_server_url = None
|
||||||
|
|
||||||
|
@ -74,7 +73,7 @@ def kill_server():
|
||||||
global _server_url
|
global _server_url
|
||||||
if _server_url != None:
|
if _server_url != None:
|
||||||
try:
|
try:
|
||||||
fp = urllib.urlopen('%sexit' % (_server_url,))
|
fp = urllib.request.urlopen('%sexit' % (_server_url,))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue