mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-12-28 15:50:21 -05:00
230 lines
7.8 KiB
Plaintext
230 lines
7.8 KiB
Plaintext
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
|
from __python__ import bound_methods, hash_literals
|
|
|
|
import traceback
|
|
from aes import GCM
|
|
from gettext import gettext as _, install
|
|
|
|
from book_list.globals import get_translations, main_js
|
|
from book_list.theme import get_font_family
|
|
from dom import ensure_id
|
|
|
|
|
|
LOADING_DOC = '''
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<script type="text/javascript" id="bootstrap">
|
|
window.iframe_entry_point = '__ENTRY_POINT__'; // different in different iframes
|
|
window.default_font_family = '__FONT__'; // from the theme
|
|
__SCRIPT__
|
|
end_script
|
|
</head>
|
|
<body>
|
|
<div style="font-size:larger; font-weight: bold; margin-top:48vh; text-align:center">
|
|
__BS__
|
|
</div>
|
|
</body>
|
|
</html>
|
|
'''.replace('end_script', '<' + '/script>') # cannot have a closing script tag as this is embedded inside a script tag in index.html
|
|
|
|
|
|
def iframe_js():
|
|
if not iframe_js.ans:
|
|
iframe_js.ans = main_js().replace(/is_running_in_iframe\s*=\s*false/, 'is_running_in_iframe = true')
|
|
main_js(None)
|
|
return iframe_js.ans
|
|
|
|
|
|
class Messenger:
|
|
|
|
def __init__(self):
|
|
self.secret = Uint8Array(64)
|
|
|
|
def reset(self):
|
|
window.crypto.getRandomValues(self.secret)
|
|
self.gcm_to_iframe = GCM(self.secret.subarray(0, 32))
|
|
self.gcm_from_iframe = GCM(self.secret.subarray(32))
|
|
|
|
def encrypt(self, data):
|
|
return self.gcm_to_iframe.encrypt(JSON.stringify(data))
|
|
|
|
def decrypt(self, data):
|
|
return JSON.parse(self.gcm_from_iframe.decrypt(data))
|
|
|
|
|
|
class IframeWrapper:
|
|
|
|
def __init__(self, handlers, iframe, entry_point, bootstrap_text, url):
|
|
self.messenger = Messenger()
|
|
self.iframe_id = ensure_id(iframe, 'content-iframe')
|
|
self.needs_init = True
|
|
self.ready = False
|
|
self.encrypted_communications = False
|
|
self.srcdoc_created = False
|
|
self.constructor_url = url
|
|
self.entry_point = entry_point
|
|
self.bootstrap_text = bootstrap_text
|
|
self.handlers = {k: handlers[k] for k in handlers}
|
|
self.on_ready_handler = self.handlers.ready
|
|
self.handlers.ready = self.on_iframe_ready
|
|
window.addEventListener('message', self.handle_message, False)
|
|
|
|
def destroy(self):
|
|
window.removeEventListener('message', self.handle_message, False)
|
|
|
|
@property
|
|
def iframe(self):
|
|
return document.getElementById(self.iframe_id)
|
|
|
|
def create_srcdoc(self):
|
|
r = /__([A-Z][A-Z_0-9]*[A-Z0-9])__/g
|
|
if self.entry_point:
|
|
data = {
|
|
'BS': self.bootstrap_text,
|
|
'SCRIPT': iframe_js(),
|
|
'FONT': get_font_family(),
|
|
'ENTRY_POINT': self.entry_point,
|
|
}
|
|
self.iframe.srcdoc = LOADING_DOC.replace(r, def(match, field): return data[field];)
|
|
else:
|
|
self.iframe.src = self.constructor_url
|
|
self.srcdoc_created = True
|
|
|
|
def init(self):
|
|
if not self.needs_init:
|
|
return
|
|
self.needs_init = False
|
|
iframe = self.iframe
|
|
if self.srcdoc_created:
|
|
if self.entry_point:
|
|
sdoc = iframe.srcdoc
|
|
iframe.srcdoc = '<p> </p>'
|
|
iframe.srcdoc = sdoc
|
|
else:
|
|
iframe.src = 'about:blank'
|
|
iframe.src = self.constructor_url
|
|
else:
|
|
self.create_srcdoc()
|
|
|
|
def reset(self):
|
|
self.ready = False
|
|
self.needs_init = True
|
|
self.encrypted_communications = False
|
|
|
|
def _send_message(self, action, encrypted, data):
|
|
data.action = action
|
|
msg = {'data':data, 'encrypted': encrypted}
|
|
if encrypted:
|
|
msg.data = self.messenger.encrypt(data)
|
|
self.iframe.contentWindow.postMessage(msg, '*')
|
|
|
|
def send_message(self, action, **data):
|
|
self._send_message(action, self.encrypted_communications, data)
|
|
|
|
def send_unencrypted_message(self, action, **data):
|
|
self._send_message(action, False, data)
|
|
|
|
def handle_message(self, event):
|
|
if event.source is not self.iframe?.contentWindow:
|
|
return
|
|
data = event.data
|
|
if self.encrypted_communications:
|
|
if data.tag is undefined:
|
|
print('Ignoring unencrypted message from iframe:', data)
|
|
return
|
|
try:
|
|
data = self.messenger.decrypt(data)
|
|
except Exception as e:
|
|
print('Could not decrypt message from iframe:')
|
|
console.log(e)
|
|
traceback.print_exc()
|
|
return
|
|
if not data.action:
|
|
return
|
|
func = self.handlers[data.action]
|
|
if func:
|
|
func(data)
|
|
else:
|
|
print('Unknown action in message from iframe to parent:', data.action)
|
|
|
|
def on_iframe_ready(self, data):
|
|
self.messenger.reset()
|
|
msg = {'secret': self.messenger.secret, 'translations': get_translations()}
|
|
self.ready = True
|
|
callback = None
|
|
if self.on_ready_handler:
|
|
callback = self.on_ready_handler(msg)
|
|
self._send_message('initialize', False, msg)
|
|
self.encrypted_communications = True
|
|
if callback:
|
|
callback()
|
|
|
|
|
|
class IframeClient:
|
|
|
|
def __init__(self, handlers):
|
|
self.encrypted_communications = False
|
|
self.handlers = {k: handlers[k] for k in handlers}
|
|
self.initialize_handler = handlers.initialize
|
|
self.handlers.initialize = self.initialize
|
|
self.ready_sent = False
|
|
window.addEventListener('message', self.handle_message, False)
|
|
window.addEventListener('load', def():
|
|
if not self.ready_sent:
|
|
self.send_message('ready', {})
|
|
self.ready_sent = True
|
|
)
|
|
|
|
def initialize(self, data):
|
|
nonlocal print
|
|
self.gcm_from_parent, self.gcm_to_parent = GCM(data.secret.subarray(0, 32)), GCM(data.secret.subarray(32))
|
|
self.encrypted_communications = True
|
|
if data.translations:
|
|
install(data.translations)
|
|
print = self.print_to_parent
|
|
if self.initialize_handler:
|
|
self.initialize_handler(data)
|
|
|
|
def print_to_parent(self, *args):
|
|
self.send_message('print', string=' '.join(map(str, args)))
|
|
|
|
def handle_message(self, event):
|
|
if event.source is not window.parent:
|
|
return
|
|
msg = event.data
|
|
data = msg.data
|
|
if msg.encrypted:
|
|
# We cannot use self.encrypted_communications as the 'display'
|
|
# message has to be unencrypted as it transports Blob objects
|
|
try:
|
|
data = JSON.parse(self.gcm_from_parent.decrypt(data))
|
|
except Exception as e:
|
|
print('Could not process message from parent:')
|
|
console.log(e)
|
|
return
|
|
if not data or not data.action:
|
|
console.log('Got a null message from parent in iframe, ignoring')
|
|
return
|
|
func = self.handlers[data.action]
|
|
if func:
|
|
try:
|
|
func(data)
|
|
except Exception as e:
|
|
console.log('Error in iframe message handler {}:'.format(data?.action))
|
|
console.log(e)
|
|
details = traceback.format_exc()
|
|
console.log(details)
|
|
self.send_message('error', title=_('Error in message handler'), details=details, msg=e.toString())
|
|
else:
|
|
print('Unknown action in message to iframe from parent: ' + data.action)
|
|
|
|
def send_message(self, action, data):
|
|
data.action = action
|
|
if self.encrypted_communications:
|
|
data = self.gcm_to_parent.encrypt(JSON.stringify(data))
|
|
window.parent.postMessage(data, '*')
|