diff --git a/src/calibre/srv/auth.py b/src/calibre/srv/auth.py index 97014515d2..c7d3d1ccd8 100644 --- a/src/calibre/srv/auth.py +++ b/src/calibre/srv/auth.py @@ -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): diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py index b32e3fc0c6..c10cd853f1 100644 --- a/src/calibre/srv/content.py +++ b/src/calibre/srv/content.py @@ -9,7 +9,7 @@ __copyright__ = '2015, Kovid Goyal ' 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 diff --git a/src/calibre/srv/http_request.py b/src/calibre/srv/http_request.py index c0e87d252c..32934cbbfc 100644 --- a/src/calibre/srv/http_request.py +++ b/src/calibre/srv/http_request.py @@ -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: diff --git a/src/calibre/srv/http_response.py b/src/calibre/srv/http_response.py index 3c97610129..5edb6f961a 100644 --- a/src/calibre/srv/http_response.py +++ b/src/calibre/srv/http_response.py @@ -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) diff --git a/src/calibre/srv/tests/ajax.py b/src/calibre/srv/tests/ajax.py index 0b50a96b10..36c343adc9 100644 --- a/src/calibre/srv/tests/ajax.py +++ b/src/calibre/srv/tests/ajax.py @@ -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 diff --git a/src/calibre/srv/tests/content.py b/src/calibre/srv/tests/content.py index bd3b19b3c1..6bc25fdbda 100644 --- a/src/calibre/srv/tests/content.py +++ b/src/calibre/srv/tests/content.py @@ -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) diff --git a/src/calibre/srv/tests/http.py b/src/calibre/srv/tests/http.py index 255d516d32..a434990ef4 100644 --- a/src/calibre/srv/tests/http.py +++ b/src/calibre/srv/tests/http.py @@ -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): # {{{ diff --git a/src/calibre/srv/utils.py b/src/calibre/srv/utils.py index 1e49628033..c87317ff73 100644 --- a/src/calibre/srv/utils.py +++ b/src/calibre/srv/utils.py @@ -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) diff --git a/src/calibre/srv/web_socket.py b/src/calibre/srv/web_socket.py index 0f76576fd2..60f35eb75a 100644 --- a/src/calibre/srv/web_socket.py +++ b/src/calibre/srv/web_socket.py @@ -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) diff --git a/src/calibre/utils/imghdr.py b/src/calibre/utils/imghdr.py index ce2903f0e5..250c774e26 100644 --- a/src/calibre/utils/imghdr.py +++ b/src/calibre/utils/imghdr.py @@ -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) diff --git a/src/calibre/utils/speedups.py b/src/calibre/utils/speedups.py index b51848e748..9b93e95ac8 100644 --- a/src/calibre/utils/speedups.py +++ b/src/calibre/utils/speedups.py @@ -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