# vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2015, Kovid Goyal from __python__ import hash_literals from gettext import gettext as _ default_timeout = 60 def set_default_timeout(val): nonlocal default_timeout default_timeout = val def encode_query_component(x): ans = encodeURIComponent(x) # The following exceptions are to make epubcfi() look better ans = ans.replace(/%2[fF]/g, '/') ans = ans.replace(/%40/g, '@') ans = ans.replace(/%5[bB]/g, '[') ans = ans.replace(/%5[dD]/g, ']') ans = ans.replace(/%5[eE]/g, '^') ans = ans.replace(/%3[aA]/g, ':') return ans def encode_query(query, qchar): if not query: return '' qchar = qchar or '?' keys = Object.keys(query).sort() ans = '' if keys.length: for k in keys: val = query[k] if val is undefined or val is None or val is '': continue ans += qchar + encodeURIComponent(k) + '=' + encode_query_component(val.toString()) qchar = '&' return ans def absolute_path(path): if path[0] is not '/': slash = '' if window.location.pathname[-1] is '/' else '/' path = window.location.pathname + slash + path return path def workaround_qt_bug(xhr, end_type, ok_code=200): if end_type is 'error' and xhr.status is ok_code and window.navigator.userAgent.indexOf('calibre-viewer') is 0: end_type = 'load' return end_type def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', query=None, timeout=None, ok_code=200, progress_totals_needed=True): # Run an AJAX request. on_complete must be a function that accepts three # arguments: end_type, xhr, ev where end_type is one of 'abort', 'error', # 'load', 'timeout'. In case end_type is anything other than 'load' you can # get a descriptive error message via `xhr.error_html`. on_progress, if # provided must be a function accepting three arguments: loaded, total, xhr # where loaded is the number of bytes received, total is the total size. # # Returns the xhr object, call xhr.send() to start the request. if timeout is None: timeout = default_timeout query = query or {} xhr = XMLHttpRequest() eq = encode_query(query) has_query = eq.length > 0 path = absolute_path(path) + eq if bypass_cache: path += ('&' if has_query else '?') + Date().getTime() xhr.request_path = path xhr.error_html = '' def set_error(event, is_network_error): if is_network_error: xhr.error_html = str.format(_('Failed to communicate with "{}", network error. Is the server running and accessible?'), xhr.request_path) elif event is 'timeout': xhr.error_html = str.format(_('Failed to communicate with "{}", timed out after: {} seconds'), xhr.request_path, timeout) elif event is 'abort': xhr.error_html = str.format(_('Failed to communicate with "{}", aborted'), xhr.request_path) else: try: rtext = xhr.responseText or '' except: rtext = '' xhr.error_html = str.format(_('Failed to communicate with "{}", with status: [{} ({})] {}

{}'), xhr.request_path, xhr.status, event, xhr.statusText, rtext[:200]) def progress_callback(ev): if ev.lengthComputable: on_progress(ev.loaded, ev.total, xhr) elif ev.loaded: if not progress_totals_needed: on_progress(ev.loaded, undefined, xhr) return ul = xhr.getResponseHeader('Calibre-Uncompressed-Length') if ul: try: ul = int(ul) except Exception: return on_progress(ev.loaded, ul, xhr) def complete_callback(end_type, ev): is_network_error = ev if end_type is 'error' else False if xhr.status is not ok_code and end_type is 'load': end_type = 'error' end_type = workaround_qt_bug(xhr, end_type, ok_code) if end_type is not 'load': set_error(end_type, is_network_error) on_complete(end_type, xhr, ev) if on_progress: xhr.addEventListener('progress', progress_callback) xhr.addEventListener('abort', def(ev): complete_callback('abort', ev);) xhr.addEventListener('error', def(ev): complete_callback('error', ev);) xhr.addEventListener('load', def(ev): complete_callback('load', ev);) xhr.addEventListener('timeout', def(ev): complete_callback('timeout', ev);) xhr.open(method, path) xhr.timeout = timeout * 1000 # IE requires timeout to be set after open return xhr def ajax_send(path, data, on_complete, on_progress=None, query=None, timeout=None, ok_code=200): # Unfortunately, browsers do not allow sending of data with HTTP GET, except # as query parameters, so we have to use POST xhr = ajax(path, on_complete, on_progress, False, 'POST', query, timeout, ok_code) xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8') xhr.send(JSON.stringify(data)) return xhr def ajax_send_file(path, file, on_complete, on_progress, timeout=None, ok_code=200): xhr = ajax(path, on_complete, on_progress, False, 'POST', timeout, ok_code) if file.type: xhr.overrideMimeType(file.type) xhr.send(file) return xhr def console_print(*args): ρσ_print(*args) # noqa: undef data = ' '.join(map(str, args)) + '\n' xhr = ajax('console-print', def():pass;, method='POST', progress_totals_needed=False) xhr.send(data) # TODO: Implement AJAX based switch user by: # 1) POST to a logout URL with an incorrect username and password # 2) Have the server accept that incorrect username/password # 3) Navigate to / which should cause the browser to re-prompt for username and password # (only problem is login dialog will likely pre-show the wrong username) To workaround that, # You can consider using a dedicated ajax based login form, see http://www.peej.co.uk/articles/http-auth-with-html-forms.html)