change setHtml() to serve content via media server

- fixes https://forums.ankiweb.net/t/deck-list-is-blank/2241/2
- fixes the security warnings on Qt 6, by ensuring our pages and
resources are coming from the same origin
This commit is contained in:
Damien Elmes 2021-10-07 22:23:00 +10:00
parent 4b5004c472
commit d33c66e195
2 changed files with 57 additions and 3 deletions

View File

@ -78,6 +78,8 @@ class MediaServer(threading.Thread):
def __init__(self, mw: aqt.main.AnkiQt) -> None:
super().__init__()
self.is_shutdown = False
# map of webview ids to pages
self._page_html: dict[int, str] = {}
def run(self) -> None:
try:
@ -119,6 +121,18 @@ class MediaServer(threading.Thread):
self._ready.wait()
return int(self.server.effective_port) # type: ignore
def set_page_html(self, id: int, html: str) -> None:
self._page_html[id] = html
def get_page_html(self, id: int) -> str | None:
return self._page_html.get(id)
def clear_page_html(self, id: int) -> None:
try:
del self._page_html[id]
except KeyError:
pass
def _handle_local_file_request(request: LocalFileRequest) -> Response:
directory = request.root
@ -220,6 +234,8 @@ def _extract_internal_request(
if dirname == "_anki":
if flask.request.method == "POST":
return _extract_collection_post_request(filename)
elif get_handler := _extract_dynamic_get_request(filename):
return get_handler
# remap legacy top-level references
base, ext = os.path.splitext(filename)
if ext == ".css":
@ -395,6 +411,7 @@ def complete_tag() -> bytes:
return aqt.mw.col.tags.complete_tag(request.data)
# these require a collection
post_handlers = {
"graphData": graph_data,
"graphPreferences": graph_preferences,
@ -435,3 +452,20 @@ def _handle_dynamic_request(request: DynamicRequest) -> Response:
return request()
except Exception as e:
return flask.make_response(str(e), HTTPStatus.INTERNAL_SERVER_ERROR)
def legacy_page_data() -> Response:
id = int(request.args["id"])
if html := aqt.mw.mediaServer.get_page_html(id):
return Response(html, mimetype="text/html")
else:
return flask.make_response("page not found", HTTPStatus.NOT_FOUND)
# this currently only handles a single method; in the future, idempotent
# requests like i18nResources should probably be moved here
def _extract_dynamic_get_request(path: str) -> DynamicRequest | None:
if path == "legacyPageData":
return legacy_page_data
else:
return None

View File

@ -120,7 +120,11 @@ class AnkiWebPage(QWebEnginePage):
def acceptNavigationRequest(
self, url: QUrl, navType: Any, isMainFrame: bool
) -> bool:
if not self.open_links_externally or "_anki/pages" in url.path():
if (
not self.open_links_externally
or "_anki/pages" in url.path()
or url.path() == "/_anki/legacyPageData"
):
return super().acceptNavigationRequest(url, navType, isMainFrame)
if not isMainFrame:
@ -315,12 +319,23 @@ class AnkiWebView(QWebEngineView):
self.show()
def _setHtml(self, html: str) -> None:
"""Send page data to media server, then surf to it.
This function used to be implemented by QWebEngine's
.setHtml() call. It is no longer used, as it has a
maximum size limit, and due to security changes, it
will stop working in the future."""
from aqt import mw
oldFocus = mw.app.focusWidget()
self._domDone = False
self._page.setHtml(html)
webview_id = id(self)
mw.mediaServer.set_page_html(webview_id, html)
self.load_url(QUrl(f"{mw.serverURL()}_anki/legacyPageData?id={webview_id}"))
# work around webengine stealing focus on setHtml()
# fixme: check which if any qt versions this is still required on
if oldFocus:
oldFocus.setFocus()
@ -646,4 +661,9 @@ document.head.appendChild(style);
Must be done on Windows prior to changing current working directory."""
self.requiresCol = False
self._domReady = False
self._page.setContent(bytes("", "ascii"))
self._page.setContent(cast(QByteArray, bytes("", "ascii")))
def __del__(self) -> None:
from aqt import mw
mw.mediaServer.clear_page_html(id(self))