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: