import email.utils import mimetypes from email.message import EmailMessage from io import BytesIO from smtplib import ( SMTP_SSL, SMTPAuthenticationError, SMTPException, SMTPHeloError, SMTPNotSupportedError, ) 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 current_user, login_user, logout_user from flask_paginate import Pagination, get_page_parameter, get_per_page_parameter from flask_wtf import FlaskForm from werkzeug.utils import secure_filename from wtforms import ( EmailField, MultipleFileField, PasswordField, StringField, SubmitField, TextAreaField, validators, ) from .model.read_mails import QMAuthError 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 LoginForm(FlaskForm): username = StringField(lazy_gettext("Username"), [validators.Email()]) password = PasswordField( lazy_gettext("Password"), [validators.Length(min=5, max=35)] ) class WriteForm(FlaskForm): send_to = EmailField("Send to", [validators.InputRequired()]) subject = StringField("Subject", [validators.InputRequired()]) cc = StringField("CC") bcc = StringField("BCC") answer_to = EmailField("Answer to") content = TextAreaField("Content") attachments = MultipleFileField("Attachments") submit = SubmitField("Send") def login(): if current_user.is_authenticated: return redirect(url_for("displayheaders"), 307) form = LoginForm() warn = "" rc = 200 if form.validate_on_submit(): if rm_login(form.username.data, form.password.data): user = JWebmailUser(form.username.data, form.password.data) add_user(user) login_user(user) nxt = request.args.get("next") if urlparse(nxt).netloc: abort(401) return redirect(nxt or url_for("displayheaders"), 303) else: warn = gettext("login failed!") elif request.method == "POST": rc = 401 return render_template("login.html", login_form=form, warn=warn), rc def logout(): logout_user() return redirect(url_for("login"), 303) def about(): view_model = { "scriptadmin": current_app.config["JWEBMAIL"]["ADMIN_MAIL"], "http_host": request.host, "request_uri": request.full_path, "remote_addr": request.remote_addr, "languages": current_app.extensions["babel"].instance.list_translations(), } return render_template("about.html", **view_model) def displayheaders(folder=""): folders = get_read_mails_logged_in().folders() if folder and folder not in folders: return render_template("error", error="no_folder", links=folders), 404 sort = request.args.get("sort", "!date") search = request.args.get("search") s = sort[1:] if sort[0] == "!" else sort if s not in ["date", "size", "sender"]: abort(400) count = get_read_mails_logged_in().count(folder) page = request.args.get(get_page_parameter(), type=int, default=1) per_page = request.args.get(get_per_page_parameter(), type=int, default=25) pgn = Pagination( page=page, per_page=per_page, total=count["total_mails"], record_name="mails", css_framework="bulma", display_msg=gettext( "displaying {start} - {end} of {total} {record_name}" ), inner_window=1, outer_window=0, ) if search: headers = get_read_mails_logged_in().search(search, folder) else: headers = get_read_mails_logged_in().read_headers_for( folder=folder, start=(pgn.page - 1) * pgn.per_page, end=(pgn.page - 1) * pgn.per_page + pgn.per_page, sort=sort, ) vals = { "folder": folder, "pgn": pgn, "msgs": headers, "mail_folders": folders, "total_size": count["byte_size"], "total_new_mails": count["unread_mails"], } return render_template("displayheaders.html", **vals) def readmail(msgid, folder=""): format = request.args.get("format", "html").lower() if format == "html": try: 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, folder=folder) elif format == "raw": path = request.args.get("path", "") content = get_read_mails_logged_in().raw(folder, msgid, path) headers = [] cd = content["head"].get("content_disposition") if cd and cd.lower() == "attachment": headers.append( ( "Content-Disposition", f"attachment; filename={content['head']['filename']}", ) ) ct = to_mime_type(content["head"]) if ct.startswith("text/"): ct += "; charset=UTF-8" headers.append(("Content-Type", ct)) return content["body"], headers elif format == "json": mail = get_read_mails_logged_in().show(folder, msgid) return mail else: abort(404) def writemail(): return render_template("writemail.html", form=WriteForm()) def move(folder=""): folders = get_read_mails_logged_in().folders() mm = request.form.getlist("mail") to_folder = request.form["select-folder"] if folder not in folders or to_folder not in folders: raise ValueError("folder not valid") for m in mm: get_read_mails_logged_in().move(m, folder, to_folder) flash(gettext("succ_move")) return redirect(url_for("displayheaders", folder=folder), 303) def remove(folder=""): folders = get_read_mails_logged_in().add_folder("Trash") mm = request.form.getlist("mail") folders = get_read_mails_logged_in().folders() if folder not in folders: raise ValueError("folder not valid") if folder == "Trash": for m in mm: get_read_mails_logged_in().remove(m, folder) else: for m in mm: get_read_mails_logged_in().move(m, folder, "Trash") flash(gettext("succ_remove")) return redirect(url_for("displayheaders", folder=folder), 303) def sendmail(): form = WriteForm() if not form.validate(): abort(400) mail = EmailMessage() mail["From"] = current_user.id mail["To"] = form.send_to.data mail["Subject"] = form.subject.data mail["Date"] = email.utils.localtime() mail["Message-ID"] = email.utils.make_msgid() if form.cc.data: mail["Cc"] = form.cc.data if form.bcc.data: mail["Bcc"] = form.bcc.data if form.answer_to.data: mail["Reply"] = form.answer_to.data mail.set_content(form.content.data) for f in form.attachments.data: if not f.filename: continue bio = BytesIO() f.save(bio) fname = secure_filename(f.filename) mt, _ce = mimetypes.guess_type(fname) if mt is None: abort(400, gettext("invalid mimetype")) maintype, subtype = mt.split("/") mail.add_attachment( bio.getvalue(), maintype, subtype, cte="base64", filename=fname ) if current_app.config["JWEBMAIL"]["WRITE_MAILS"].get("DEBUG"): print(mail.as_string()) flash(gettext("succ_send")) return redirect(url_for("displayheaders"), 303) _, send_server = current_user.id.split("@") send_server = "mail." + send_server try: with SMTP_SSL(send_server) as smtp: smtp.login(current_user.id, current_user.password) smtp.send_message(mail) except ( SMTPHeloError, SMTPAuthenticationError, SMTPNotSupportedError, SMTPException, ): return ( render_template("writemail.html", form=form, warning=gettext("error_send")), 400, ) flash(gettext("succ_send")) return redirect(url_for("displayheaders"), 303)