diff --git a/aqt/browser.py b/aqt/browser.py index 00ed44228..505d64df2 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -15,7 +15,7 @@ import aqt.forms from anki.utils import fmtTimeSpan, ids2str, stripHTMLMedia, isWin, intTime, isMac from aqt.utils import saveGeom, restoreGeom, saveSplitter, restoreSplitter, \ saveHeader, restoreHeader, saveState, restoreState, applyStyles, getTag, \ - showInfo, askUser, tooltip, openHelp, showWarning, shortcut, getBase, mungeQA + showInfo, askUser, tooltip, openHelp, showWarning, shortcut, mungeQA from anki.hooks import runHook, addHook, remHook from aqt.webview import AnkiWebView from aqt.toolbar import Toolbar @@ -1089,7 +1089,7 @@ where id in %s""" % ids2str(sf)) txt = c.a() txt = re.sub("\[\[type:[^]]+\]\]", "", txt) ti = lambda x: x - base = getBase(self.mw.col) + base = self.mw.baseHTML() self._previewWeb.stdHtml( ti(mungeQA(self.col, txt)), self.mw.reviewer._styles(), bodyClass="card card%d" % (c.ord+1), head=base, diff --git a/aqt/clayout.py b/aqt/clayout.py index 3ada121b9..993889142 100644 --- a/aqt/clayout.py +++ b/aqt/clayout.py @@ -8,7 +8,7 @@ from aqt.qt import * from anki.consts import * import aqt from anki.sound import playFromText, clearAudioQueue -from aqt.utils import saveGeom, restoreGeom, getBase, mungeQA,\ +from aqt.utils import saveGeom, restoreGeom, mungeQA,\ showInfo, askUser, getOnlyText, \ showWarning, openHelp, downArrow from anki.utils import isMac, isWin, joinFields @@ -225,7 +225,7 @@ Please create a new card type first.""")) def renderPreview(self): c = self.card ti = self.maybeTextInput - base = getBase(self.mw.col) + base = self.mw.baseHTML() self.tab['pform'].frontWeb.stdHtml( ti(mungeQA(self.mw.col, c.q(reload=True))), self.mw.reviewer._styles(), bodyClass="card card%d" % (c.ord+1), head=base), diff --git a/aqt/editor.py b/aqt/editor.py index 33016c6ff..a0a05b00c 100644 --- a/aqt/editor.py +++ b/aqt/editor.py @@ -14,7 +14,7 @@ import anki.sound from anki.hooks import runHook, runFilter from aqt.sound import getAudio from aqt.webview import AnkiWebView -from aqt.utils import shortcut, showInfo, showWarning, getBase, getFile, \ +from aqt.utils import shortcut, showInfo, showWarning, getFile, \ openHelp, tooltip, downArrow import aqt import anki.js @@ -370,7 +370,7 @@ class Editor(object): """ % dict(flds=_("Fields"), cards=_("Cards")) self.web.stdHtml(_html % ( - getBase(self.mw.col), anki.js.jquery, + self.mw.baseHTML(), anki.js.jquery, topbuts, _("Show Duplicates"))) diff --git a/aqt/main.py b/aqt/main.py index 44dc23486..438e286ec 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -18,6 +18,7 @@ import aqt.progress import aqt.webview import aqt.toolbar import aqt.stats +import aqt.mediasrv from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \ restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \ openHelp, openLink, checkInvalidFilename @@ -79,6 +80,7 @@ class AnkiQt(QMainWindow): self.setupHooks() self.setupRefreshTimer() self.updateTitleBar() + self.setupMediaServer() # screens self.setupDeckBrowser() self.setupOverview() @@ -1157,3 +1159,14 @@ Please ensure a profile is open and Anki is not busy, then try again."""), def _onCollect(self): gc.collect() + + # Media server + ########################################################################## + # prevent malicious decks from accessing the local filesystem + + def setupMediaServer(self): + self.mediaServer = aqt.mediasrv.MediaServer() + self.mediaServer.start() + + def baseHTML(self): + return '' % self.mediaServer.port diff --git a/aqt/mediasrv.py b/aqt/mediasrv.py new file mode 100644 index 000000000..9e4151169 --- /dev/null +++ b/aqt/mediasrv.py @@ -0,0 +1,58 @@ +# Copyright: Damien Elmes +# -*- coding: utf-8 -*- +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +from aqt.qt import * +from http import HTTPStatus +import http.server +import errno + +class MediaServer(QThread): + + def run(self): + self.port = 8080 + self.server = None + while not self.server: + try: + self.server = http.server.HTTPServer( + ("localhost", self.port), RequestHandler) + except OSError as e: + if e.errno == errno.EADDRINUSE: + self.port += 1 + continue + raise + break + self.server.serve_forever() + +class RequestHandler(http.server.SimpleHTTPRequestHandler): + + def send_head(self): + path = self.translate_path(self.path) + if os.path.isdir(path): + self.send_error(HTTPStatus.NOT_FOUND, "File not found") + return None + ctype = self.guess_type(path) + try: + f = open(path, 'rb') + except OSError: + self.send_error(HTTPStatus.NOT_FOUND, "File not found") + return None + try: + self.send_response(HTTPStatus.OK) + self.send_header("Content-type", ctype) + fs = os.fstat(f.fileno()) + self.send_header("Content-Length", str(fs[6])) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + self.end_headers() + return f + except: + f.close() + raise + + def log_message(self, format, *args): + if not os.getenv("ANKIDEV"): + return + print("%s - - [%s] %s" % + (self.address_string(), + self.log_date_time_string(), + format%args)) diff --git a/aqt/reviewer.py b/aqt/reviewer.py index e2d617aee..bf7ce7709 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -10,10 +10,10 @@ import html.parser from anki.lang import _, ngettext from aqt.qt import * -from anki.utils import stripHTML, isMac, json +from anki.utils import stripHTML, json from anki.hooks import addHook, runHook from anki.sound import playFromText, clearAudioQueue, play -from aqt.utils import mungeQA, getBase, openLink, tooltip, askUserDialog, \ +from aqt.utils import mungeQA, tooltip, askUserDialog, \ downArrow from aqt.sound import getAudio import aqt @@ -166,7 +166,7 @@ function _typeAnsPress() { def _initWeb(self): self._reps = 0 self._bottomReady = False - base = getBase(self.mw.col) + base = self.mw.baseHTML() # main window self.web.onLoadFinished = self._showQuestion self.web.stdHtml(self._revHtml, self._styles(), head=base) diff --git a/aqt/utils.py b/aqt/utils.py index ed9ea65d6..cc1781ca4 100644 --- a/aqt/utils.py +++ b/aqt/utils.py @@ -338,16 +338,10 @@ def applyStyles(widget): if os.path.exists(p): widget.setStyleSheet(open(p).read()) +# this will go away in the future - please use mw.baseHTML() instead def getBase(col): - base = None - mdir = col.media.dir() - if isWin and not mdir.startswith("\\\\"): - prefix = "file:///" - else: - prefix = "file://" - mdir = mdir.replace("\\", "/") - base = prefix + urllib.parse.quote(mdir) + "/" - return '' % base + from aqt import mw + return mw.baseHTML() def openFolder(path): if isWin: