py3: misc fixes for the server

20 out of 34 tests for the server now pass
This commit is contained in:
Kovid Goyal 2019-04-14 19:22:50 +05:30
parent 2e8f88394b
commit 333dab88c2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
11 changed files with 60 additions and 33 deletions

View File

@ -68,12 +68,18 @@ def as_bytestring(x):
return x
def as_unicodestring(x):
if isinstance(x, bytes):
x = x.decode('utf-8')
return x
def md5_hex(s):
return md5(as_bytestring(s)).hexdigest().decode('ascii')
return as_unicodestring(md5(as_bytestring(s)).hexdigest())
def sha256_hex(s):
return sha256(as_bytestring(s)).hexdigest().decode('ascii')
return as_unicodestring(sha256(as_bytestring(s)).hexdigest())
def base64_decode(s):

View File

@ -9,7 +9,7 @@ __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import os, errno
from io import BytesIO
from threading import Lock
from polyglot.builtins import map
from polyglot.builtins import map, unicode_type
from functools import partial
from calibre import fit_image, sanitize_file_name
@ -167,7 +167,7 @@ def book_filename(rd, book_id, mi, fmt, as_encoded_unicode=False):
if as_encoded_unicode:
# See https://tools.ietf.org/html/rfc6266
fname = sanitize_file_name(fname).encode('utf-8')
fname = quote(fname).decode('ascii')
fname = unicode_type(quote(fname))
else:
fname = ascii_filename(fname).replace('"', '_')
return fname

View File

@ -266,6 +266,7 @@ class HTTPRequest(Connection):
try:
method, uri, req_protocol = line.strip().split(b' ', 2)
req_protocol = req_protocol.decode('ascii')
rp = int(req_protocol[5]), int(req_protocol[7])
self.method = method.decode('ascii').upper()
except Exception:

View File

@ -13,7 +13,7 @@ from itertools import chain, repeat
from operator import itemgetter
from functools import wraps
from polyglot.builtins import iteritems, itervalues, reraise, map, is_py3
from polyglot.builtins import iteritems, itervalues, reraise, map, is_py3, unicode_type, string_or_bytes
from calibre import guess_type, force_unicode
from calibre.constants import __version__, plugins, ispy3
@ -247,8 +247,7 @@ class RequestData(object): # {{{
def filesystem_file_with_custom_etag(self, output, *etag_parts):
etag = hashlib.sha1()
string = type('')
tuple(map(lambda x:etag.update(string(x)), etag_parts))
tuple(map(lambda x:etag.update(unicode_type(x).encode('utf-8')), etag_parts))
return ETaggedFile(output, etag.hexdigest())
def filesystem_file_with_constant_etag(self, output, etag_as_hexencoded_string):
@ -312,7 +311,10 @@ class ReadableOutput(object):
def filesystem_file_output(output, outheaders, stat_result):
etag = getattr(output, 'etag', None)
if etag is None:
etag = hashlib.sha1(type('')(stat_result.st_mtime) + force_unicode(output.name or '')).hexdigest()
oname = output.name or ''
if not isinstance(oname, string_or_bytes):
oname = unicode_type(oname)
etag = hashlib.sha1((unicode_type(stat_result.st_mtime) + force_unicode(oname)).encode('utf-8')).hexdigest()
else:
output = output.output
etag = '"%s"' % etag
@ -356,7 +358,7 @@ class GeneratedOutput(object):
class StaticOutput(object):
def __init__(self, data):
if isinstance(data, type('')):
if isinstance(data, unicode_type):
data = data.encode('utf-8')
self.data = data
self.etag = '"%s"' % hashlib.sha1(data).hexdigest()
@ -649,12 +651,17 @@ class HTTPConnection(HTTPRequest):
if stat_result is not None:
output = filesystem_file_output(output, outheaders, stat_result)
if 'Content-Type' not in outheaders:
mt = guess_type(output.name)[0]
output_name = output.name
if not isinstance(output_name, string_or_bytes):
output_name = unicode_type(output_name)
mt = guess_type(output_name)[0]
if mt:
if mt in {'text/plain', 'text/html', 'application/javascript', 'text/css'}:
mt += '; charset=UTF-8'
outheaders['Content-Type'] = mt
elif isinstance(output, (bytes, type(''))):
else:
outheaders['Content-Type'] = 'application/octet-stream'
elif isinstance(output, string_or_bytes):
output = dynamic_output(output, outheaders)
elif hasattr(output, 'read'):
output = ReadableOutput(output)

View File

@ -168,7 +168,7 @@ class ContentTest(LibraryBaseTest):
ae = self.assertEqual
def a(filename, data=None, status=OK, method='POST', username='12', add_duplicates='n', job_id=1):
r, data = make_request(conn, '/cdb/add-book/{}/{}/{}'.format(job_id, add_duplicates, quote(filename.encode('utf-8')).decode('ascii')),
r, data = make_request(conn, '/cdb/add-book/{}/{}/{}'.format(job_id, add_duplicates, quote(filename.encode('utf-8'))),
username=username, password='test', prefix='', method=method, data=data)
ae(status, r.status)
return data

View File

@ -43,8 +43,8 @@ class ContentTest(LibraryBaseTest):
missing('/%s/C:/out.html' % prefix, b'Naughty, naughty!')
def test_response(r):
self.assertIn(b'max-age=', r.getheader('Cache-Control'))
self.assertIn(b'public', r.getheader('Cache-Control'))
self.assertIn('max-age=', r.getheader('Cache-Control'))
self.assertIn('public', r.getheader('Cache-Control'))
self.assertIsNotNone(r.getheader('Expires'))
self.assertIsNotNone(r.getheader('ETag'))
self.assertIsNotNone(r.getheader('Content-Type'))
@ -182,7 +182,7 @@ class ContentTest(LibraryBaseTest):
self.ae(r.status, http_client.OK)
self.ae(data, db.cover(2))
self.ae(r.getheader('Used-Cache'), 'no')
path = from_hex_unicode(r.getheader('Tempfile')).decode('utf-8')
path = from_hex_unicode(r.getheader('Tempfile'))
f, fdata = share_open(path, 'rb'), data
# Now force an update
change_cover(1)
@ -190,7 +190,7 @@ class ContentTest(LibraryBaseTest):
self.ae(r.status, http_client.OK)
self.ae(data, db.cover(2))
self.ae(r.getheader('Used-Cache'), 'no')
path = from_hex_unicode(r.getheader('Tempfile')).decode('utf-8')
path = from_hex_unicode(r.getheader('Tempfile'))
f2, f2data = share_open(path, 'rb'), data
# Do it again
change_cover(2)

View File

@ -175,7 +175,7 @@ class TestHTTP(BaseTest):
self.ae(r.getheader('Content-Length'), str(len(body)))
self.ae(r.getheader('Content-Type'), 'text/plain; charset=UTF-8')
self.ae(len(r.getheaders()), 3)
self.ae(r.read(), '')
self.ae(r.read(), b'')
conn.request('GET', '/choose')
r = conn.getresponse()
self.ae(r.status, http_client.NOT_FOUND)
@ -200,7 +200,7 @@ class TestHTTP(BaseTest):
r = conn.getresponse()
self.ae(r.status, http_client.MOVED_PERMANENTLY)
self.ae(r.getheader('Location'), '/somewhere-else')
self.ae('', r.read())
self.ae(b'', r.read())
server.change_handler(lambda data:data.path[0] + data.read().decode('ascii'))
conn = server.connect(timeout=base_timeout * 5)
@ -277,7 +277,10 @@ class TestHTTP(BaseTest):
for i in range(10):
conn._HTTPConnection__state = http_client._CS_IDLE
conn.request('GET', '/%d'%i)
responses.append(conn.response_class(conn.sock, strict=conn.strict, method=conn._method))
if ispy3:
responses.append(conn.response_class(conn.sock, method=conn._method))
else:
responses.append(conn.response_class(conn.sock, strict=conn.strict, method=conn._method))
for i in range(10):
r = responses[i]
r.begin()
@ -288,7 +291,7 @@ class TestHTTP(BaseTest):
server.loop.opts.timeout = 10 # ensure socket is not closed because of timeout
conn.request('GET', '/close', headers={'Connection':'close'})
r = conn.getresponse()
self.ae(r.status, 200), self.ae(r.read(), 'close')
self.ae(r.status, 200), self.ae(r.read(), b'close')
server.loop.wakeup()
num = 10
while num and server.loop.num_active_connections != 0:
@ -302,8 +305,8 @@ class TestHTTP(BaseTest):
conn = server.connect(timeout=1)
conn.request('GET', '/something')
r = conn.getresponse()
self.ae(r.status, 200), self.ae(r.read(), 'something')
self.assertIn('Request Timeout', eintr_retry_call(conn.sock.recv, 500))
self.ae(r.status, 200), self.ae(r.read(), b'something')
self.assertIn(b'Request Timeout', eintr_retry_call(conn.sock.recv, 500))
# }}}
def test_http_response(self): # {{{

View File

@ -284,13 +284,13 @@ def get_translator_for_lang(cache, bcp_47_code):
def encode_path(*components):
'Encode the path specified as a list of path components using URL encoding'
return '/' + '/'.join(urlquote(x.encode('utf-8'), '').decode('ascii') for x in components)
return '/' + '/'.join(urlquote(x.encode('utf-8'), '') for x in components)
class Cookie(SimpleCookie):
def _BaseCookie__set(self, key, real_value, coded_value):
if not isinstance(key, bytes):
if not ispy3 and not isinstance(key, bytes):
key = key.encode('ascii') # Python 2.x cannot handle unicode keys
return SimpleCookie._BaseCookie__set(self, key, real_value, coded_value)

View File

@ -295,7 +295,7 @@ class WebSocketConnection(HTTPConnection):
if self.method != 'GET':
return self.simple_response(http_client.BAD_REQUEST, 'Invalid WebSocket method: %s' % self.method)
response = HANDSHAKE_STR % as_base64_unicode(sha1(key + GUID_STR).digest())
response = HANDSHAKE_STR % as_base64_unicode(sha1((key + GUID_STR).encode('utf-8')).digest())
self.optimize_for_sending_packet()
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
self.set_state(WRITE, self.upgrade_connection_to_ws, ReadOnlyFileBuffer(response.encode('ascii')), inheaders)

View File

@ -7,6 +7,7 @@ from __future__ import (unicode_literals, division, absolute_import,
from struct import unpack, error
import os
from calibre.utils.speedups import ReadOnlyFileBuffer
from calibre.constants import ispy3
from polyglot.builtins import string_or_bytes
""" Recognize image file formats and sizes based on their first few bytes."""
@ -125,16 +126,23 @@ def jpeg_dimensions(stream):
raise ValueError('Truncated JPEG data')
return ans
x = b''
if ispy3:
def read_byte():
return read(1)[0]
else:
def read_byte():
return ord(read(1)[0])
x = None
while True:
# Find next marker
while x != b'\xff':
x = read(1)
while x != 0xff:
x = read_byte()
# Soak up padding
marker = b'\xff'
while marker == b'\xff':
marker = read(1)
q = ord(marker[0]) # [0] needed for memoryview
marker = 0xff
while marker == 0xff:
marker = read_byte()
q = marker
if 0xc0 <= q <= 0xcf and q != 0xc4 and q != 0xcc:
# SOFn marker
stream.seek(3, os.SEEK_CUR)

View File

@ -100,6 +100,8 @@ def svg_path_to_painter_path(d):
while data.tell() < end:
last_cmd = cmd
cmd = data.read(1) if repeated_command is None else repeated_command
if isinstance(cmd, memoryview):
cmd = cmd.tobytes()
repeated_command = None
if cmd == b' ':
@ -176,7 +178,7 @@ def svg_path_to_painter_path(d):
x1, y1 = x, y
x, y = parse_floats(2, x, y)
path.quadTo(x1, y1, x, y)
elif cmd[0] in b'-.' or b'0' <= cmd[0] <= b'9':
elif cmd[0:1] in b'-.0123456789':
# A new number begins
# In this case, multiple parameters tuples are specified for the last command
# We rewind to reparse data correctly