From f9954bc4e58e60e1a1787105725cdfcb7443906c Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 7 Apr 2025 18:41:37 +0000 Subject: [PATCH] smoketest fs-access when transcoding the thumbnailer / audio transcoder could return misleading errors if the operation fails due to insufficient filesystem permissions try reading a few bytes from the file and bail early if it fails, and detect/log unwritable output folders for thumbnails also fixes http-response to only return svg-formatted errors if the initial request expects a picture in response, not audio --- copyparty/httpcli.py | 10 ++++++++-- copyparty/th_cli.py | 27 +++++++++++++++++++++++---- copyparty/th_srv.py | 13 ++++++++++--- copyparty/web/browser.js | 11 ++++++++++- docs/rice/README.md | 6 ------ 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index c3ed6783..92c98922 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -4882,7 +4882,7 @@ class HttpCli(object): self.reply(pt.encode("utf-8"), status=rc) return True - if "th" in self.ouparam: + if "th" in self.ouparam and str(self.ouparam["th"])[:1] in "jw": return self.tx_svg("e" + pt[:3]) # most webdav clients will not send credentials until they @@ -5810,7 +5810,13 @@ class HttpCli(object): thp = None if self.thumbcli and not nothumb: - thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt) + try: + thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt) + except Pebkac as ex: + if ex.code == 500 and th_fmt[:1] in "jw": + self.log("failed to convert [%s]:\n%s" % (abspath, ex), 3) + return self.tx_svg("--error--\ncheck\nserver\nlog") + raise if thp: return self.tx_file(thp) diff --git a/copyparty/th_cli.py b/copyparty/th_cli.py index 45017ee2..cfb148c2 100644 --- a/copyparty/th_cli.py +++ b/copyparty/th_cli.py @@ -1,13 +1,15 @@ # coding: utf-8 from __future__ import print_function, unicode_literals +import errno import os +import stat from .__init__ import TYPE_CHECKING from .authsrv import VFS from .bos import bos from .th_srv import EXTS_AC, HAVE_WEBP, thumb_path -from .util import Cooldown +from .util import Cooldown, Pebkac if True: # pylint: disable=using-constant-test from typing import Optional, Union @@ -16,6 +18,9 @@ if TYPE_CHECKING: from .httpsrv import HttpSrv +IOERROR = "reading the file was denied by the server os; either due to filesystem permissions, selinux, apparmor, or similar:\n%r" + + class ThumbCli(object): def __init__(self, hsrv: "HttpSrv") -> None: self.broker = hsrv.broker @@ -124,7 +129,7 @@ class ThumbCli(object): tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa) tpaths = [tpath] - if fmt == "w": + if fmt[:1] == "w": # also check for jpg (maybe webp is unavailable) tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg") @@ -157,8 +162,22 @@ class ThumbCli(object): if abort: return None - if not bos.path.getsize(os.path.join(ptop, rem)): - return None + ap = os.path.join(ptop, rem) + try: + st = bos.stat(ap) + if not st.st_size or not stat.S_ISREG(st.st_mode): + return None + + with open(ap, "rb", 4) as f: + if not f.read(4): + raise Exception() + except OSError as ex: + if ex.errno == errno.ENOENT: + raise Pebkac(404) + else: + raise Pebkac(500, IOERROR % (ex,)) + except Exception as ex: + raise Pebkac(500, IOERROR % (ex,)) x = self.broker.ask("thumbsrv.get", ptop, rem, mtime, fmt) return x.get() # type: ignore diff --git a/copyparty/th_srv.py b/copyparty/th_srv.py index 279af264..206592ee 100644 --- a/copyparty/th_srv.py +++ b/copyparty/th_srv.py @@ -385,8 +385,12 @@ class ThumbSrv(object): self.log(msg, c) if getattr(ex, "returncode", 0) != 321: if fun == funs[-1]: - with open(ttpath, "wb") as _: - pass + try: + with open(ttpath, "wb") as _: + pass + except Exception as ex: + t = "failed to create the file [%s]: %r" + self.log(t % (ttpath, ex), 3) else: # ffmpeg may spawn empty files on windows try: @@ -399,7 +403,10 @@ class ThumbSrv(object): try: wrename(self.log, ttpath, tpath, vn.flags) - except: + except Exception as ex: + if not os.path.exists(tpath): + t = "failed to move [%s] to [%s]: %r" + self.log(t % (ttpath, tpath, ex), 3) pass with self.mutex: diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 05700924..e9510474 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -316,6 +316,7 @@ var Ls = { "mm_eunk": "Unknown Errol", "mm_e404": "Could not play audio; error 404: File not found.", "mm_e403": "Could not play audio; error 403: Access denied.\n\nTry pressing F5 to reload, maybe you got logged out", + "mm_e500": "Could not play audio; error 500: Check server logs.", "mm_e5xx": "Could not play audio; server error ", "mm_nof": "not finding any more audio files nearby", "mm_prescan": "Looking for music to play next...", @@ -919,6 +920,7 @@ var Ls = { "mm_eunk": "Ukjent feil", "mm_e404": "Avspilling feilet: Fil ikke funnet.", "mm_e403": "Avspilling feilet: Tilgang nektet.\n\nKanskje du ble logget ut?\nPrøv å trykk F5 for å laste siden på nytt.", + "mm_e500": "Avspilling feilet: Rusk i maskineriet, sjekk serverloggen.", "mm_e5xx": "Avspilling feilet: ", "mm_nof": "finner ikke flere sanger i nærheten", "mm_prescan": "Leter etter neste sang...", @@ -1522,6 +1524,7 @@ var Ls = { "mm_eunk": "未知错误", "mm_e404": "无法播放音频;错误 404:文件未找到。", "mm_e403": "无法播放音频;错误 403:访问被拒绝。\n\n尝试按 F5 重新加载,也许你已被注销", + "mm_e500": "无法播放音频;错误 500:检查服务器日志。", //m "mm_e5xx": "无法播放音频;服务器错误", "mm_nof": "附近找不到更多音频文件", "mm_prescan": "正在寻找下一首音乐...", @@ -4202,6 +4205,7 @@ function evau_error(e) { } var em = '' + eplaya.error.message, mfile = '\n\nFile: «' + uricom_dec(eplaya.src.split('/').pop()) + '»', + e500 = L.mm_e500, e404 = L.mm_e404, e403 = L.mm_e403; @@ -4214,6 +4218,9 @@ function evau_error(e) { if (em.startsWith('404: ')) err = e404; + if (em.startsWith('500: ')) + err = e500; + toast.warn(15, esc(basenames(err + mfile))); console.log(basenames(err + mfile)); @@ -4225,7 +4232,9 @@ function evau_error(e) { if (this.status < 400) return; - err = this.status == 403 ? e403 : this.status == 404 ? e404 : + err = this.status == 403 ? e403 : + this.status == 404 ? e404 : + this.status == 500 ? e500 : L.mm_e5xx + this.status; toast.warn(15, esc(basenames(err + mfile))); diff --git a/docs/rice/README.md b/docs/rice/README.md index 76d223ac..9f59485f 100644 --- a/docs/rice/README.md +++ b/docs/rice/README.md @@ -33,12 +33,6 @@ if you are introducing a new ttf/woff font, don't forget to declare the font its } ``` -and because textboxes don't inherit fonts by default, you can force it like this: - -```css -input[type=text], input[type=submit], input[type=button] { font-family: var(--font-main) } -``` - and if you want to have a monospace font in the fancy markdown editor, do this: ```css