diff --git a/app/api/views/alias.py b/app/api/views/alias.py index 25f0dfdc..c6c21550 100644 --- a/app/api/views/alias.py +++ b/app/api/views/alias.py @@ -1,26 +1,18 @@ +from email.utils import parseaddr + from flask import g -from flask import jsonify, request +from flask import jsonify +from flask import request from flask_cors import cross_origin from app.api.base import api_bp, verify_api_key +from app.config import EMAIL_DOMAIN from app.config import PAGE_LIMIT from app.dashboard.views.alias_log import get_alias_log from app.dashboard.views.index import get_alias_info, AliasInfo from app.extensions import db -from app.models import GenEmail, ForwardEmail, ForwardEmailLog -from app.utils import random_string -import re - -from flask import render_template, request, redirect, url_for, flash -from flask_login import login_required, current_user -from flask_wtf import FlaskForm -from wtforms import StringField, validators, ValidationError - -from app.config import EMAIL_DOMAIN -from app.dashboard.base import dashboard_bp -from app.email_utils import get_email_part -from app.extensions import db from app.log import LOG +from app.models import ForwardEmailLog from app.models import GenEmail, ForwardEmail from app.utils import random_string @@ -310,7 +302,7 @@ def create_contact_route(alias_id): if not ForwardEmail.get_by(reply_email=reply_email): break - website_email = get_email_part(contact_email) + _, website_email = parseaddr(contact_email) # already been added if ForwardEmail.get_by(gen_email_id=gen_email.id, website_email=website_email): diff --git a/app/dashboard/views/alias_contact_manager.py b/app/dashboard/views/alias_contact_manager.py index 2caf84be..0db9f93a 100644 --- a/app/dashboard/views/alias_contact_manager.py +++ b/app/dashboard/views/alias_contact_manager.py @@ -1,4 +1,5 @@ import re +from email.utils import parseaddr from flask import render_template, request, redirect, url_for, flash from flask_login import login_required, current_user @@ -7,7 +8,6 @@ from wtforms import StringField, validators, ValidationError from app.config import EMAIL_DOMAIN from app.dashboard.base import dashboard_bp -from app.email_utils import get_email_part from app.extensions import db from app.log import LOG from app.models import GenEmail, ForwardEmail @@ -76,7 +76,7 @@ def alias_contact_manager(alias_id, forward_email_id=None): if not ForwardEmail.get_by(reply_email=reply_email): break - website_email = get_email_part(contact_email) + _, website_email = parseaddr(contact_email) # already been added if ForwardEmail.get_by( diff --git a/app/email_utils.py b/app/email_utils.py index 2099d169..8d6b857c 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -1,7 +1,5 @@ -import email import os -from email.message import EmailMessage, Message -from email.mime.application import MIMEApplication +from email.message import Message from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText @@ -233,28 +231,6 @@ def send_email( smtp.sendmail(SUPPORT_EMAIL, to_email, msg_raw) -def get_email_name(email_from): - """parse email from header and return the name part - First Last -> First Last - ab@cd.com -> "" - """ - if "<" in email_from: - return email_from[: email_from.find("<")].strip() - - return "" - - -def get_email_part(email_from): - """parse email from header and return the email part - First Last -> ab@cd.com - ab@cd.com -> "" - """ - if "<" in email_from: - return email_from[email_from.find("<") + 1 : email_from.find(">")].strip() - - return email_from - - def get_email_local_part(email): """ Get the local part from email diff --git a/app/models.py b/app/models.py index 8b02d430..82535b56 100644 --- a/app/models.py +++ b/app/models.py @@ -1,6 +1,7 @@ import enum import random import uuid +from email.utils import parseaddr, formataddr import arrow import bcrypt @@ -17,7 +18,6 @@ from app.config import ( AVATAR_URL_EXPIRATION, JOB_ONBOARDING_1, ) - from app.extensions import db from app.log import LOG from app.oauth_models import Scope @@ -729,14 +729,20 @@ class ForwardEmail(db.Model, ModelMixin): def website_send_to(self): """return the email address with name. to use when user wants to send an email from the alias""" - from app.email_utils import get_email_name + + name = self.website_email.replace("@", " at ") if self.website_from: - name = get_email_name(self.website_from) - if name: - return name + " " + self.website_email + f" <{self.reply_email}>" + website_name, _ = parseaddr(self.website_from) - return self.website_email.replace("@", " at ") + f" <{self.reply_email}>" + if website_name: + # remove all double quote + website_name = website_name.replace('"', "") + name = website_name + " | " + name + + return f'"{name}" <{self.reply_email}>' + # cannot use formataddr here as this field is for email client, not for MTA + # return formataddr((self.website_email.replace("@", " at "), self.reply_email)) def last_reply(self) -> "ForwardEmailLog": """return the most recent reply""" diff --git a/email_handler.py b/email_handler.py index b98143b4..231e75b2 100644 --- a/email_handler.py +++ b/email_handler.py @@ -30,14 +30,15 @@ It should contain the following info: """ -import uuid import time +import uuid from email import encoders from email.message import Message from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.parser import Parser from email.policy import SMTPUTF8 +from email.utils import parseaddr, formataddr from io import BytesIO from smtplib import SMTP from typing import Optional @@ -53,8 +54,6 @@ from app.config import ( POSTFIX_SUBMISSION_TLS, ) from app.email_utils import ( - get_email_name, - get_email_part, send_email, add_dkim_signature, get_email_domain_part, @@ -214,7 +213,7 @@ def get_or_create_forward_email( """ website_from_header can be the full-form email, i.e. "First Last " """ - website_email = get_email_part(website_from_header) + _, website_email = parseaddr(website_from_header) forward_email = ForwardEmail.get_by( gen_email_id=gen_email.id, website_email=website_email ) @@ -333,13 +332,13 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str: # so it can pass DMARC check # replace the email part in from: header website_from_header = msg["From"] - website_email = get_email_part(website_from_header) - from_header = ( - get_email_name(website_from_header) - + ("" if get_email_name(website_from_header) == "" else " - ") + website_name, website_email = parseaddr(website_from_header) + new_website_name = ( + website_name + + (" - " if website_name else "") + website_email.replace("@", " at ") - + f" <{forward_email.reply_email}>" ) + from_header = formataddr((new_website_name, forward_email.reply_email)) add_or_replace_header(msg, "From", from_header) LOG.d("new from header:%s", from_header) diff --git a/tests/test_email_utils.py b/tests/test_email_utils.py index 27e8b2a1..fe013150 100644 --- a/tests/test_email_utils.py +++ b/tests/test_email_utils.py @@ -1,9 +1,6 @@ from email.message import EmailMessage from app.email_utils import ( - get_email_name, - get_email_part, - get_email_local_part, get_email_domain_part, email_belongs_to_alias_domains, can_be_used_as_personal_email, @@ -14,24 +11,6 @@ from app.extensions import db from app.models import User, CustomDomain -def test_get_email_name(): - assert get_email_name("First Last ") == "First Last" - assert get_email_name("First Last") == "First Last" - assert get_email_name(" First Last ") == "First Last" - assert get_email_name("ab@cd.com") == "" - - -def test_get_email_part(): - assert get_email_part("First Last ") == "ab@cd.com" - assert get_email_part("First Last") == "ab@cd.com" - assert get_email_part(" First Last ") == "ab@cd.com" - assert get_email_part("ab@cd.com") == "ab@cd.com" - - -def test_get_email_local_part(): - assert get_email_local_part("ab@cd.com") == "ab" - - def test_get_email_domain_part(): assert get_email_domain_part("ab@cd.com") == "cd.com"