summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJannis M. Hoffmann <jannis@fehcom.de>2023-12-04 23:15:38 +0100
committerJannis M. Hoffmann <jannis@fehcom.de>2023-12-04 23:15:38 +0100
commit4a954eb8e70bda5819f63da9cd841f1579573413 (patch)
tree913d796501ab93fb3c6f81a23b2703a76f520f71
parentf5283a7bb0b215b8a426219f9be8f2d4f9d5b59f (diff)
add multi language urls
update translations use redis as session store add requirements.txt file to pin dependencies add proper subfolder support improve compatibility with python 3.9 correct js for login focus
-rw-r--r--pyproject.toml1
-rw-r--r--requirements.txt150
-rw-r--r--src/jwebmail/__init__.py53
-rw-r--r--src/jwebmail/model/read_mails.py13
-rw-r--r--src/jwebmail/read_mails.py65
-rw-r--r--src/jwebmail/render_mail.py4
-rw-r--r--src/jwebmail/templates/_main_table.html4
-rw-r--r--src/jwebmail/templates/about.html6
-rw-r--r--src/jwebmail/templates/login.html8
-rw-r--r--src/jwebmail/translations/de/LC_MESSAGES/messages.po106
-rw-r--r--src/jwebmail/webmail.py139
11 files changed, 335 insertions, 214 deletions
diff --git a/pyproject.toml b/pyproject.toml
index 7761898..22c7ece 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,6 +11,7 @@ dependencies = [
"Flask-WTF",
"flask-paginate",
"email-validator",
+ "redis",
]
[project.optional-dependencies]
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..27e1c43
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,150 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# pip-compile --generate-hashes --strip-extras
+#
+babel==2.13.1 \
+ --hash=sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900 \
+ --hash=sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed
+ # via flask-babel
+blinker==1.7.0 \
+ --hash=sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9 \
+ --hash=sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182
+ # via flask
+click==8.1.7 \
+ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
+ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
+ # via flask
+dnspython==2.4.2 \
+ --hash=sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8 \
+ --hash=sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984
+ # via email-validator
+email-validator==2.1.0.post1 \
+ --hash=sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44 \
+ --hash=sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637
+ # via jwebmail (pyproject.toml)
+flask==3.0.0 \
+ --hash=sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638 \
+ --hash=sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58
+ # via
+ # flask-babel
+ # flask-login
+ # flask-paginate
+ # flask-wtf
+ # jwebmail (pyproject.toml)
+flask-babel==4.0.0 \
+ --hash=sha256:638194cf91f8b301380f36d70e2034c77ee25b98cb5d80a1626820df9a6d4625 \
+ --hash=sha256:dbeab4027a3f4a87678a11686496e98e1492eb793cbdd77ab50f4e9a2602a593
+ # via jwebmail (pyproject.toml)
+flask-login==0.6.3 \
+ --hash=sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333 \
+ --hash=sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d
+ # via jwebmail (pyproject.toml)
+flask-paginate==2023.10.24 \
+ --hash=sha256:48cc477fd64c95b6c7be980bc96198a8eadeca863484f78c1e1fe22608fc3b28 \
+ --hash=sha256:57790fd4c543c802511ade71b6a9d9654e7295fcde39eda00d3cd4aa3bd68859
+ # via jwebmail (pyproject.toml)
+flask-wtf==1.2.1 \
+ --hash=sha256:8bb269eb9bb46b87e7c8233d7e7debdf1f8b74bf90cc1789988c29b37a97b695 \
+ --hash=sha256:fa6793f2fb7e812e0fe9743b282118e581fb1b6c45d414b8af05e659bd653287
+ # via jwebmail (pyproject.toml)
+idna==3.6 \
+ --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
+ --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
+ # via email-validator
+itsdangerous==2.1.2 \
+ --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \
+ --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a
+ # via
+ # flask
+ # flask-wtf
+jinja2==3.1.2 \
+ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
+ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
+ # via
+ # flask
+ # flask-babel
+markupsafe==2.1.3 \
+ --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
+ --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
+ --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
+ --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
+ --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \
+ --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
+ --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
+ --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \
+ --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \
+ --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
+ --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
+ --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
+ --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
+ --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
+ --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
+ --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
+ --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \
+ --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
+ --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
+ --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
+ --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
+ --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
+ --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
+ --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
+ --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
+ --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \
+ --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
+ --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
+ --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
+ --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
+ --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
+ --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
+ --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
+ --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \
+ --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
+ --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
+ --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \
+ --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
+ --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \
+ --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
+ --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
+ --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
+ --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
+ --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
+ --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
+ --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
+ --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
+ --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
+ --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
+ --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
+ --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
+ --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
+ --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
+ --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
+ --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
+ --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
+ --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
+ --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \
+ --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \
+ --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11
+ # via
+ # jinja2
+ # werkzeug
+ # wtforms
+pytz==2023.3.post1 \
+ --hash=sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b \
+ --hash=sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7
+ # via flask-babel
+redis==5.0.1 \
+ --hash=sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f \
+ --hash=sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f
+ # via jwebmail (pyproject.toml)
+werkzeug==3.0.1 \
+ --hash=sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc \
+ --hash=sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10
+ # via
+ # flask
+ # flask-login
+wtforms==3.1.1 \
+ --hash=sha256:5e51df8af9a60f6beead75efa10975e97768825a82146a65c7cbf5b915990620 \
+ --hash=sha256:ae7c54b29806c70f7bce8eb9f24afceb10ca5c32af3d9f04f74d2f66ccc5c7e0
+ # via flask-wtf
diff --git a/src/jwebmail/__init__.py b/src/jwebmail/__init__.py
index db0c796..b5279fb 100644
--- a/src/jwebmail/__init__.py
+++ b/src/jwebmail/__init__.py
@@ -1,8 +1,8 @@
-import os.path as ospath
+import os.path as os_path
import pwd
import sys
-from flask import Flask
+from flask import Flask, g
from flask_babel import Babel, get_locale
from flask_login import LoginManager, login_required
from jinja2 import ChainableUndefined
@@ -21,6 +21,7 @@ from .webmail import (
readmail,
sendmail,
writemail,
+ DEFAULT_LANGUAGE,
)
if sys.version_info >= (3, 11):
@@ -37,9 +38,9 @@ def validate_config(app):
assert "@" in conf["JWEBMAIL"]["ADMIN_MAIL"]
assert pwd.getpwnam(conf["JWEBMAIL"]["READ_MAILS"]["MAILBOX_USER"])
- assert ospath.isdir(conf["JWEBMAIL"]["READ_MAILS"]["MAILBOX"])
- assert ospath.isfile(conf["JWEBMAIL"]["READ_MAILS"]["AUTHENTICATOR"])
- assert ospath.isfile(conf["JWEBMAIL"]["READ_MAILS"]["BACKEND"])
+ assert os_path.isdir(conf["JWEBMAIL"]["READ_MAILS"]["MAILBOX"])
+ assert os_path.isfile(conf["JWEBMAIL"]["READ_MAILS"]["AUTHENTICATOR"])
+ assert os_path.isfile(conf["JWEBMAIL"]["READ_MAILS"]["BACKEND"])
def create_app():
@@ -49,7 +50,7 @@ def create_app():
app.config.from_file("../../jwebmail.toml", load=toml_load, text=False)
validate_config(app)
- Babel(app, locale_selector=lambda: "de")
+ Babel(app, locale_selector=lambda: g.get("lang_code", DEFAULT_LANGUAGE))
app.cli.add_command(compile_css_command)
@@ -58,32 +59,62 @@ def create_app():
login_manager.user_loader(load_user)
login_manager.init_app(app)
+ add_view_funcs(app)
+ route(app)
+
@app.context_processor
def inject_version():
return {"version": "4.0", "get_locale": get_locale, "format_mail": format_mail}
- add_view_funcs(app)
- route(app)
+ @app.url_defaults
+ def add_language_code(endpoint, values):
+ if "lang_code" in values:
+ return
+ if app.url_map.is_endpoint_expecting(endpoint, "lang_code"):
+ values["lang_code"] = g.get("lang_code", DEFAULT_LANGUAGE)
+
+ @app.url_value_preprocessor
+ def pull_lang_code(endpoint, values):
+ g.lang_code = (
+ values.pop("lang_code", DEFAULT_LANGUAGE) if values else DEFAULT_LANGUAGE
+ )
return app
def route(app):
app.add_url_rule("/", view_func=login, methods=["GET", "POST"])
+ app.add_url_rule("/<lang_code>/", view_func=login, methods=["GET", "POST"])
app.add_url_rule("/about", view_func=about)
+ app.add_url_rule("/<lang_code>/about", view_func=about)
+
app.add_url_rule("/logout", view_func=logout)
+ app.add_url_rule("/<lang_code>/logout", view_func=logout)
dh = login_required(displayheaders)
app.add_url_rule("/home/", view_func=dh)
app.add_url_rule("/home/<folder>", view_func=dh)
+ app.add_url_rule("/<lang_code>/home/", view_func=dh)
+ app.add_url_rule("/<lang_code>/home/<folder>", view_func=dh)
+ lr_readmail = login_required(readmail)
+ app.add_url_rule("/read/<msgid>", endpoint="read", view_func=lr_readmail)
+ app.add_url_rule("/read/<folder>/<msgid>", endpoint="read", view_func=lr_readmail)
app.add_url_rule(
- "/read/<msgid>", endpoint="read", view_func=login_required(readmail)
+ "/<lang_code>/read/<msgid>", endpoint="read", view_func=lr_readmail
)
- app.add_url_rule("/raw/<msgid>", endpoint="raw", view_func=login_required(rawmail))
+ app.add_url_rule(
+ "/<lang_code>/read/<folder>/<msgid>", endpoint="read", view_func=lr_readmail
+ )
+
+ lr_rawmail = login_required(rawmail)
+ app.add_url_rule("/raw/<msgid>", endpoint="raw", view_func=rawmail)
+ app.add_url_rule("/raw/<folder>/<msgid>", endpoint="raw", view_func=rawmail)
- app.add_url_rule("/write", endpoint="write", view_func=login_required(writemail))
+ lr_writemail = login_required(writemail)
+ app.add_url_rule("/write", endpoint="write", view_func=lr_writemail)
+ app.add_url_rule("/<lang_code>/write", endpoint="write", view_func=lr_writemail)
app.add_url_rule(
"/write", endpoint="send", view_func=login_required(sendmail), methods=["POST"]
)
diff --git a/src/jwebmail/model/read_mails.py b/src/jwebmail/model/read_mails.py
index f82b601..291fa1e 100644
--- a/src/jwebmail/model/read_mails.py
+++ b/src/jwebmail/model/read_mails.py
@@ -31,13 +31,12 @@ class QMailAuthuser:
shell=True,
timeout=2,
)
- match completed_proc.returncode:
- case 0:
- return True
- case 1:
- return False
- case n:
- raise QMAuthError("authentication error", n)
+ if completed_proc.returncode == 0:
+ return True
+ if completed_proc.returncode == 1:
+ return False
+ else:
+ raise QMAuthError("authentication error", completed_proc.returncode)
except TimeoutExpired:
return False
diff --git a/src/jwebmail/read_mails.py b/src/jwebmail/read_mails.py
index e1c3b8c..915567c 100644
--- a/src/jwebmail/read_mails.py
+++ b/src/jwebmail/read_mails.py
@@ -1,11 +1,17 @@
-import dbm
-import shelve
-
+import redis
from flask import current_app, g
-from flask_login import current_user
+from flask_login import UserMixin, current_user
from .model.read_mails import QMailAuthuser
+EXPIRATION_SEC = 60 * 60 * 25
+
+
+class JWebmailUser(UserMixin):
+ def __init__(self, mail_addr, password):
+ self.id = mail_addr
+ self.password = password
+
def build_qma(username, password):
authenticator = current_app.config["JWEBMAIL"]["READ_MAILS"]["AUTHENTICATOR"]
@@ -22,29 +28,52 @@ def login(username, password):
return build_qma(username, password).verify_user()
-def add_user(user):
- with shelve.open("user_sessions", flag="c") as s:
- s[user.get_id()] = user
+def add_user(user: JWebmailUser):
+ passwd = current_app.config["JWEBMAIL"]["READ_MAILS"]["SESSION_STORE_PASSWD"]
+ r = redis.Redis(
+ host="localhost",
+ port=6379,
+ decode_responses=True,
+ protocol=3,
+ username="jwebmail",
+ password=passwd,
+ )
+ r.setex(f"jwm:user:{user.get_id()}", EXPIRATION_SEC, user.password)
-def load_user(username):
- try:
- with shelve.open("user_sessions", flag="r") as s:
- user = s[username]
- return user
- except dbm.error:
- return None
- except KeyError:
+def load_user(username: str) -> JWebmailUser:
+ passwd = current_app.config["JWEBMAIL"]["READ_MAILS"]["SESSION_STORE_PASSWD"]
+ r = redis.Redis(
+ host="localhost",
+ port=6379,
+ decode_responses=True,
+ protocol=3,
+ username="jwebmail",
+ password=passwd,
+ )
+ passwd = r.getex(f"jwm:user:{username}", EXPIRATION_SEC)
+ if passwd is None:
return None
+ return JWebmailUser(username, passwd)
def get_read_mails_logged_in():
if "read_mails" in g:
return g.read_mails
- with shelve.open("user_sessions", flag="r") as s:
- user_data = s[current_user.get_id()]
+ passwd = current_app.config["JWEBMAIL"]["READ_MAILS"]["SESSION_STORE_PASSWD"]
+ r = redis.Redis(
+ host="localhost",
+ port=6379,
+ decode_responses=True,
+ protocol=3,
+ username="jwebmail",
+ password=passwd,
+ )
+ passwd = r.get(f"jwm:user:{current_user.get_id()}")
+ if passwd is None:
+ raise KeyError(current_user.get_id())
- qma = build_qma(current_user.get_id(), user_data.password)
+ qma = build_qma(current_user.get_id(), passwd)
g.read_mails = qma
return qma
diff --git a/src/jwebmail/render_mail.py b/src/jwebmail/render_mail.py
index 0e5406c..804b200 100644
--- a/src/jwebmail/render_mail.py
+++ b/src/jwebmail/render_mail.py
@@ -143,7 +143,9 @@ def mime_render(maintype, subtype, content, path):
renderer = MIMERenderSubs.get((maintype, subtype)) or MIMERenderSubs.get(maintype)
if not renderer:
- return f'<p class="jwm-body-unsupported">Unsupported MIME type of <code>{maintype}/{subtype}</code>.</p>\n'
+ typ = f"<code>{maintype}/{subtype}</code>"
+ msg = gettext("Unsupported MIME type of {mime_type}.").format(mime_type=typ)
+ return f'<p class="jwm-body-unsupported">{msg}</p>\n'
return renderer(subtype, content, path)
diff --git a/src/jwebmail/templates/_main_table.html b/src/jwebmail/templates/_main_table.html
index 02f9f81..b331087 100644
--- a/src/jwebmail/templates/_main_table.html
+++ b/src/jwebmail/templates/_main_table.html
@@ -18,7 +18,9 @@
</div>
<div class="column is-10">
- <a href="{{ url_for('read', msgid=msg.message_handle) }}">{{ msg.head.subject or '_' }}</a>
+ <a href="{{ url_for('read', msgid=msg.message_handle, folder=folder) }}">
+ {{ msg.head.subject or '_' }}
+ </a>
</div>
<div class="column is-2">
diff --git a/src/jwebmail/templates/about.html b/src/jwebmail/templates/about.html
index e197275..836b668 100644
--- a/src/jwebmail/templates/about.html
+++ b/src/jwebmail/templates/about.html
@@ -40,7 +40,11 @@
<ul>
{% for lang in languages %}
<li>
- {{ get_locale().languages[lang.language] }}
+ {% if lang == get_locale() %}
+ <a href="{{ url_for('about', lang_code=lang.language) }}"><strong>{{ get_locale().languages[lang.language] }}</strong></a>
+ {% else %}
+ <a href="{{ url_for('about', lang_code=lang.language) }}">{{ get_locale().languages[lang.language] }}</a>
+ {% endif %}
</li>
{% endfor %}
</ul>
diff --git a/src/jwebmail/templates/login.html b/src/jwebmail/templates/login.html
index 2915de3..d73238d 100644
--- a/src/jwebmail/templates/login.html
+++ b/src/jwebmail/templates/login.html
@@ -26,7 +26,7 @@
</div>
{% endif %}
- <form name="login1" method="POST" class="pure-form pure-form-aligned jwm-round">
+ <form name="login1" id="login1" method="POST" class="pure-form pure-form-aligned jwm-round">
{{ login_form.csrf_token }}
@@ -74,10 +74,10 @@
{% block scripts %}
<script type="text/javascript">
- if (!document.login1.userid.value) {
- document.login1.userid.focus();
+ if (!document.forms.login1.username.value) {
+ document.forms.login1.username.focus();
} else {
- document.login1.password.focus();
+ document.forms.login1.password.focus();
}
</script>
{% endblock %}
diff --git a/src/jwebmail/translations/de/LC_MESSAGES/messages.po b/src/jwebmail/translations/de/LC_MESSAGES/messages.po
index ad48d16..48b1dca 100644
--- a/src/jwebmail/translations/de/LC_MESSAGES/messages.po
+++ b/src/jwebmail/translations/de/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2023-11-26 19:21+0100\n"
+"POT-Creation-Date: 2023-12-04 22:59+0100\n"
"PO-Revision-Date: 2023-11-23 12:18+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@@ -18,150 +18,160 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.13.1\n"
-#: jwebmail/render_mail.py:63
+#: src/jwebmail/render_mail.py:67
msgid "Attachment {filename} of type {filetype}"
msgstr "Anhang {filename} des types {filetype}"
-#: jwebmail/render_mail.py:113
+#: src/jwebmail/render_mail.py:117
msgid "From"
msgstr "Von"
-#: jwebmail/render_mail.py:114
+#: src/jwebmail/render_mail.py:118
msgid "To"
-msgstr "Fuer"
+msgstr "Für"
-#: jwebmail/render_mail.py:115
+#: src/jwebmail/render_mail.py:119
msgid "CC"
msgstr ""
-#: jwebmail/render_mail.py:116
+#: src/jwebmail/render_mail.py:120
msgid "BCC"
msgstr ""
-#: jwebmail/webmail.py:39
+#: src/jwebmail/render_mail.py:147
+msgid "Unsupported MIME type of {mime_type}."
+msgstr "Nicht unterstützter MIME-Typ {mime_type}."
+
+#: src/jwebmail/webmail.py:27
msgid "Username"
msgstr "Nutzername"
-#: jwebmail/webmail.py:41
+#: src/jwebmail/webmail.py:29
msgid "Password"
msgstr "Passwort"
-#: jwebmail/webmail.py:75
-msgid "login failed"
-msgstr "Login fehlgeschlagen"
+#: src/jwebmail/webmail.py:64
+msgid "login failed!"
+msgstr "Login fehlgeschlagen!"
-#: jwebmail/webmail.py:120
+#: src/jwebmail/webmail.py:111
msgid "displaying <b>{start} - {end}</b> of <b>{total}</b> {record_name}"
msgstr "zeige <b>{start} - {end}</b> von <b>{total}</b> {record_name} an"
-#: jwebmail/webmail.py:173
+#: src/jwebmail/webmail.py:164
msgid "succ_move"
msgstr "erfolgreich Verschoben"
-#: jwebmail/webmail.py:221
+#: src/jwebmail/webmail.py:211
msgid "error_send"
msgstr "Fehler beim senden"
-#: jwebmail/webmail.py:223
+#: src/jwebmail/webmail.py:213
msgid "succ_send"
msgstr "erfolgreich Verschoben"
-#: jwebmail/templates/_bot_nav.html:11
+#: src/jwebmail/templates/_bot_nav.html:11
msgid "Move to"
msgstr "Verschiebe nach"
-#: jwebmail/templates/_bot_nav.html:19 jwebmail/templates/_folders.html:21
+#: src/jwebmail/templates/_bot_nav.html:19
+#: src/jwebmail/templates/_folders.html:21
msgid "Home"
msgstr "Ursprung"
-#: jwebmail/templates/_bot_nav.html:26
+#: src/jwebmail/templates/_bot_nav.html:26
msgid "Move"
msgstr "Verschieben"
-#: jwebmail/templates/_bot_nav.html:38
+#: src/jwebmail/templates/_bot_nav.html:38
msgid "Remove"
-msgstr "Loeschen"
+msgstr "Löschen"
-#: jwebmail/templates/_bot_nav.html:44
+#: src/jwebmail/templates/_bot_nav.html:44
msgid "check all"
msgstr "alle markieren"
-#: jwebmail/templates/_folders.html:36
+#: src/jwebmail/templates/_folders.html:36
#, python-format
msgid "%(total_new_mails)s new"
msgstr "%(total_new_mails)s neu"
-#: jwebmail/templates/_folders.html:41
+#: src/jwebmail/templates/_folders.html:41
msgid "mailbox size: "
-msgstr "mailbox groesse: "
+msgstr "mailbox größe: "
-#: jwebmail/templates/_top_nav.html:4
+#: src/jwebmail/templates/_top_nav.html:4
msgid "Logout"
msgstr "Abmelden"
-#: jwebmail/templates/_top_nav.html:5
+#: src/jwebmail/templates/_top_nav.html:5
msgid "Write"
msgstr "Schreiben"
-#: jwebmail/templates/_top_nav.html:11
+#: src/jwebmail/templates/_top_nav.html:11
msgid "Search"
msgstr "Suchen"
-#: jwebmail/templates/_top_nav.html:26
+#: src/jwebmail/templates/_top_nav.html:26
msgid "Sort"
msgstr "Sortieren"
-#: jwebmail/templates/_top_nav.html:32 jwebmail/templates/_top_nav.html:33
+#: src/jwebmail/templates/_top_nav.html:32
+#: src/jwebmail/templates/_top_nav.html:33
msgid "Date"
msgstr "Datum"
-#: jwebmail/templates/_top_nav.html:32 jwebmail/templates/_top_nav.html:34
-#: jwebmail/templates/_top_nav.html:35
+#: src/jwebmail/templates/_top_nav.html:32
+#: src/jwebmail/templates/_top_nav.html:34
+#: src/jwebmail/templates/_top_nav.html:35
msgid "Descending"
msgstr "Absteigend"
-#: jwebmail/templates/_top_nav.html:33 jwebmail/templates/_top_nav.html:36
+#: src/jwebmail/templates/_top_nav.html:33
+#: src/jwebmail/templates/_top_nav.html:36
msgid "Ascending"
msgstr "Aufsteigend"
-#: jwebmail/templates/_top_nav.html:34
+#: src/jwebmail/templates/_top_nav.html:34
msgid "Size"
-msgstr "Groesse"
+msgstr "Größe"
-#: jwebmail/templates/_top_nav.html:35 jwebmail/templates/_top_nav.html:36
+#: src/jwebmail/templates/_top_nav.html:35
+#: src/jwebmail/templates/_top_nav.html:36
msgid "Sender"
msgstr "Sender"
-#: jwebmail/templates/about.html:57 jwebmail/templates/login.html:13
-#: jwebmail/templates/login.html:69
+#: src/jwebmail/templates/about.html:61 src/jwebmail/templates/login.html:8
+#: src/jwebmail/templates/login.html:64
msgid "Login"
msgstr "Anmelden"
-#: jwebmail/templates/displayheaders.html:25
+#: src/jwebmail/templates/displayheaders.html:25
msgid "This folder is empty!"
msgstr "Dieses Verzeichnis ist leer!"
-#: jwebmail/templates/mainlayout.html:22
+#: src/jwebmail/templates/mainlayout.html:22
msgid "About"
-msgstr "Ueber"
+msgstr "Über"
-#: jwebmail/templates/mainlayout.html:25
+#: src/jwebmail/templates/mainlayout.html:25
msgid "Version"
msgstr "Version"
-#: jwebmail/templates/not_found.html:19
+#: src/jwebmail/templates/not_found.html:19
msgid "start page"
msgstr "Startseite"
-#: jwebmail/templates/readmail.html:12 jwebmail/templates/writemail.html:78
+#: src/jwebmail/templates/readmail.html:12
+#: src/jwebmail/templates/writemail.html:78
msgid "back"
-msgstr "zurueck"
+msgstr "zurück"
-#: jwebmail/templates/writemail.html:62
+#: src/jwebmail/templates/writemail.html:62
msgid "attach file"
-msgstr "Datei anhaengen"
+msgstr "Datei anhängen"
-#: jwebmail/templates/writemail.html:71
+#: src/jwebmail/templates/writemail.html:71
msgid "Send"
msgstr "Senden"
diff --git a/src/jwebmail/webmail.py b/src/jwebmail/webmail.py
index 4e47dbd..2cfc834 100644
--- a/src/jwebmail/webmail.py
+++ b/src/jwebmail/webmail.py
@@ -2,7 +2,7 @@ from urllib.parse import urlparse
from flask import abort, current_app, flash, redirect, render_template, request, url_for
from flask_babel import gettext, lazy_gettext
-from flask_login import UserMixin, current_user, login_user, logout_user
+from flask_login import current_user, login_user, logout_user
from flask_paginate import Pagination, get_page_parameter, get_per_page_parameter
from flask_wtf import FlaskForm
from wtforms import (
@@ -16,15 +16,11 @@ from wtforms import (
)
from .model.read_mails import QMAuthError
-from .read_mails import add_user, get_read_mails_logged_in
+from .read_mails import JWebmailUser, add_user, get_read_mails_logged_in
from .read_mails import login as rm_login
from .render_mail import to_mime_type
-
-class JWebmailUser(UserMixin):
- def __init__(self, mail_addr, password):
- self.id = mail_addr
- self.password = password
+DEFAULT_LANGUAGE = "de"
class LoginForm(FlaskForm):
@@ -51,6 +47,7 @@ def login():
form = LoginForm()
warn = ""
+ rc = 200
if form.validate_on_submit():
if rm_login(form.username.data, form.password.data):
@@ -58,15 +55,17 @@ def login():
add_user(user)
login_user(user)
- next = request.args.get("next")
+ nxt = request.args.get("next")
- if urlparse(next).netloc:
+ if urlparse(nxt).netloc:
abort(401)
- return redirect(next or url_for("displayheaders"), 303)
+ return redirect(nxt or url_for("displayheaders"), 303)
else:
- warn = gettext("login failed")
+ warn = gettext("login failed!")
+ elif request.method == "POST":
+ rc = 401
- return render_template("login.html", login_form=form, warn=warn), 401
+ return render_template("login.html", login_form=form, warn=warn), rc
def logout():
@@ -137,13 +136,13 @@ def displayheaders(folder=""):
return render_template("displayheaders.html", **vals)
-def readmail(msgid):
+def readmail(msgid, folder=""):
try:
- mail = get_read_mails_logged_in().show("", msgid)
+ mail = get_read_mails_logged_in().show(folder, msgid)
except QMAuthError:
return render_template("not_found.html"), 404
- return render_template("readmail.html", msg=mail)
+ return render_template("readmail.html", msg=mail, folder=folder)
def writemail():
@@ -166,10 +165,10 @@ def move(folder):
return redirect(url_for("displayheaders"), 303)
-def rawmail(msgid):
+def rawmail(msgid, folder=""):
path = request.args.get("path", "")
- content = get_read_mails_logged_in().raw("", msgid, path)
+ content = get_read_mails_logged_in().raw(folder, msgid, path)
headers = []
@@ -213,109 +212,3 @@ def sendmail():
flash(gettext("succ_send"))
return redirect(url_for("displayheaders"), 303)
-
-
-"""
-sub remove {
- my $self = shift;
-
- my $v = $self->validation;
- $v->csrf_protect;
- $v->required('mail');
-
- if ($v->has_error) {
- $self->reply->exception('errors in ' . join('', $v->failed->@*));
- return;
- }
-
- my $auth = $self->stash(STS_AUTH);
-
- my $mm = $self->every_param('mail');
- my $folder = $self->stash('folder');
-
- $self->users->remove($auth, $folder, $_) for @$mm;
-
- $self->res->code(303);
- $self->redirect_to('displayheaders');
-}
-
-
-### session password handling
-
-use constant { S_PASSWD => 'pw', S_OTP_S3D_PW => 'otp_s3d_pw' };
-
-sub _rand_data {
- my $len = shift;
-
- if (TRUE_RANDOM) {
- #return makerandom_octet(Length => $len, Strength => 0); # was used for Crypt::Random
- return urandom($len);
- }
- else {
- my $res = '';
- for (0..$len-1) {
- vec($res, $_, 8) = int rand 256;
- }
-
- return $res;
- }
-}
-
-sub _session_passwd {
- my ($self, $passwd, $challenge) = @_;
- my $secAlg = LOGIN_SCHEME;
-
- $self->_warn_crypt;
-
- if (defined $passwd) { # set
- if ($secAlg eq fc 'cram_md5') {
- $self->session(S_PASSWD() => $passwd, challenge => $challenge);
- }
- elsif ($secAlg eq fc 'plain') {
- unless ($passwd) {
- $self->s3d(S_PASSWD, '');
- delete $self->session->{S_OTP_S3D_PW()};
- return;
- }
- die "'$passwd' contains invalid character \\n" if $passwd =~ /\n/;
- if (length $passwd < 20) {
- $passwd .= "\n" . ' ' x (20 - length($passwd) - 1);
- }
- my $passwd_utf8 = encode('UTF-8', $passwd);
- my $rand_bytes = _rand_data(length $passwd_utf8);
- $self->s3d(S_PASSWD, b64_encode($passwd_utf8 ^ $rand_bytes, ''));
- $self->session(S_OTP_S3D_PW, b64_encode($rand_bytes, ''));
- }
- else {
- die
- }
- }
- else { # get
- if ($secAlg eq fc 'cram_md5') {
- wantarray or carp "you forgot the challenge";
- return ($self->session(S_PASSWD), $self->session('challenge'));
- }
- elsif ($secAlg eq fc 'plain') {
- my $pw = b64_decode($self->s3d(S_PASSWD) || '');
- my $otp = b64_decode($self->session(S_OTP_S3D_PW) || '');
- my ($res) = split "\n", decode('UTF-8', $pw ^ $otp), 2;
- return $res;
- }
- else {
- die
- }
- }
-}
-
-sub _warn_crypt {
- my $self = shift;
-
- state $once = 0;
-
- if ( !TRUE_RANDOM && !$once && LOGIN_SCHEME eq fc 'plain' ) {
- $self->log->warn("Falling back to pseudo random generation. Please install Crypt::URandom");
- $once = 1;
- }
-}
-
-"""