From 0237cc4bac0dafa9a3f3013b3f48e4f2941963bd Mon Sep 17 00:00:00 2001 From: "Jannis M. Hoffmann" Date: Thu, 12 Dec 2024 16:41:13 +0100 Subject: fixes and improvements for error handling --- src/jwebmail/__init__.py | 2 +- src/jwebmail/model/read_mails.py | 61 ++++++++++++++++++++++++++++++++-------- src/jwebmail/read_mails.py | 2 +- src/jwebmail/webmail.py | 9 ++++-- 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/jwebmail/__init__.py b/src/jwebmail/__init__.py index b4c01a1..904c3b8 100644 --- a/src/jwebmail/__init__.py +++ b/src/jwebmail/__init__.py @@ -36,7 +36,7 @@ else: toml_read_file = dict(load=toml_load, text=True) -__version__ = "2.8.2.dev1" +__version__ = "2.8.3.dev0" csrf = CSRFProtect() diff --git a/src/jwebmail/model/read_mails.py b/src/jwebmail/model/read_mails.py index 534f2f7..c9cee7a 100644 --- a/src/jwebmail/model/read_mails.py +++ b/src/jwebmail/model/read_mails.py @@ -1,5 +1,6 @@ import os from base64 import b64decode +from functools import wraps from socket import socketpair import varlink @@ -11,6 +12,28 @@ class QMAuthError(Exception): self.rc = rc +class JWebmailMailStorageError(Exception): + def __init__(self, err, params): + self.name = err + self.parameters = params + + +def remap_errors(func): + prefix = "de.jmhoffmann.jwebmail.mail-storage." + + @wraps(func) + def decorator(*args, **kvargs): + try: + return func(*args, **kvargs) + except varlink.VarlinkError as ex: + if (name := ex.error().removeprefix(prefix)) != ex.error(): + raise JWebmailMailStorageError(name, ex.parameters()) + else: + raise + + return decorator + + class QMailAuthuser: def __init__(self, prog, mailbox_path, virtual_user, authenticator): self._prog = prog @@ -23,6 +46,7 @@ class QMailAuthuser: self._client = None self._connection = None + @remap_errors def list_search(self, folder, bound, after, limit, sort, search): sort_val = dict() if sort[0] == "!": @@ -64,6 +88,7 @@ class QMailAuthuser: req["last"], ) + @remap_errors def count(self, folder): resp = self._connection.Stats(folder=folder) return { @@ -72,6 +97,7 @@ class QMailAuthuser: "unread_mails": resp["unread_count"], } + @remap_errors def show(self, folder, msgid): resp = self._connection.Show(folder=folder, mid=msgid) return { @@ -79,6 +105,7 @@ class QMailAuthuser: "body": self._mail_body(resp["mail"]["body"]), } + @remap_errors def raw(self, folder, mid, path): resp = self._connection.Raw(folder=folder, mid=mid, path=path) return { @@ -86,18 +113,22 @@ class QMailAuthuser: "body": b64decode(resp["body"]), } + @remap_errors def folders(self): resp = self._connection.Folders() return list(resp["folders"]) + [""] + @remap_errors def move(self, mid, from_f, to_f): self._connection.Move(mid=mid, from_folder=from_f, to_folder=to_f) return True + @remap_errors def remove(self, folder, msgid): self._connection.Remove(folder=folder, mid=msgid) return True + @remap_errors def add_folder(self, name): resp = self._connection.AddFolder(name=name) return resp["status"] @@ -176,8 +207,8 @@ class QMailAuthuser: assert False def open(self, username, password): - (rp, wp) = os.pipe() - (sp, sc) = socketpair() + rp, wp = os.pipe() + sp, sc = socketpair() cmdline = [self._authenticator, self._prog] if (pid := os.fork()) == 0: # child @@ -204,24 +235,32 @@ class QMailAuthuser: "de.jmhoffmann.jwebmail.mail-storage", connection=sp ) except ConnectionResetError: - (pid, status) = os.waitpid(pid, os.WNOHANG) - if pid != 0: - raise QMAuthError(os.waitstatus_to_exitcode(status)) + pid, status = os.waitpid(self._pid, 0) + if (rc := os.waitstatus_to_exitcode(status)) != 0: + raise QMAuthError(rc) else: raise user = username[: username.index("@")] - self._connection.Init( - unix_user=self._virtual_user, - mailbox_path=os.path.join(self._mailbox_path, user), - ) + try: + self._connection.Init( + unix_user=self._virtual_user, + mailbox_path=os.path.join(self._mailbox_path, user), + ) + except BrokenPipeError: + pid, status = os.waitpid(self._pid, 0) + assert pid == self._pid + if (rc := os.waitstatus_to_exitcode(status)) != 0: + raise QMAuthError(rc) + else: + raise return self def close(self): self._connection.close() self._socket.close() - (pid, status) = os.waitpid(self._pid, 0) + pid, status = os.waitpid(self._pid, 0) assert pid == self._pid rc = os.waitstatus_to_exitcode(status) if rc != 0: @@ -234,7 +273,7 @@ class QMailAuthuser: if ex_val is None: self.close() elif issubclass(ex_type, BrokenPipeError): - (pid, _status) = os.waitpid(self._pid, 0) + pid, _status = os.waitpid(self._pid, 0) assert pid == self._pid return False diff --git a/src/jwebmail/read_mails.py b/src/jwebmail/read_mails.py index 8e1a23d..f88ce46 100644 --- a/src/jwebmail/read_mails.py +++ b/src/jwebmail/read_mails.py @@ -173,8 +173,8 @@ def _build_qma(domain): def login(username, password): + _, domain = username.split("@") try: - _, domain = username.split("@") _build_qma(domain).open(username, password) except QMAuthError as err: if err.rc == 1: diff --git a/src/jwebmail/webmail.py b/src/jwebmail/webmail.py index d260e8a..6c0b6c2 100644 --- a/src/jwebmail/webmail.py +++ b/src/jwebmail/webmail.py @@ -26,7 +26,7 @@ from wtforms import ( validators, ) -from .model.read_mails import QMAuthError +from .model.read_mails import JWebmailMailStorageError from .read_mails import get_read_mails_logged_in from .read_mails import login as rm_login from .render_mail import to_mime_type @@ -165,8 +165,11 @@ def readmail(msgid, folder=""): if format == "html": try: mail = read_mails.show(folder, msgid) - except QMAuthError: - return render_template("not_found.html"), 404 + except JWebmailMailStorageError as ex: + if ex.name == "InvalidMID": + return render_template("not_found.html"), 404 + else: + raise return render_template("readmail.html", msg=mail, folder=folder) -- cgit v1.2.3