def make_report(spam_list, conf, mbox): datestr = datetime.now().strftime("%A, %d. %B %Y") spam_list = sorted(spam_list, key=lambda s: float(s.score)) total_size = len(spam_list) pwd = os.path.dirname(os.path.realpath(__file__)) logo = base64.b64encode(open(pwd + "/logo.png", "rb").read()).decode("utf-8") msg = email.mime.text.MIMEText( make_report_header(logo, datestr, total_size) + make_report_body(spam_list) + make_report_footer(), 'html', "utf-8") msg['From'] = "%s <%s>" % (conf.from_name, conf.from_address) msg['To'] = "%s <%s>" % (mbox, mbox) msg['Subject'] = "Quarantine Report for %s" % str(mbox) msg['Date'] = email.utils.formatdate(localtime=1) msg.policy = EmailPolicy() return msg
def update_mail_to_from(bytes_data, rcpttos, mailfrom): msg = message_from_bytes(bytes_data, policy=EmailPolicy(utf8=True, linesep='\r\n')) logging.debug('Removing To: %s', msg['To']) del msg['To'] msg['To'] = rcpttos logging.debug('Added To: %s', msg['To']) logging.debug('Removing From: %s', msg['From']) del msg['From'] msg['From'] = mailfrom logging.debug('Added From: %s', msg['From']) mail_bytes = msg.as_bytes() logging.debug('update_mail_to_from got %i bytes and returned %i bytes', len(bytes_data), len(mail_bytes)) return mail_bytes
from flask import request, current_app from html2text import html2text from string import Template from project.tasks.mail import celery_send_mail from email.policy import EmailPolicy from project.models import MailTemplate import flask_mail flask_mail.message_policy = EmailPolicy(linesep='\r\n', refold_source='none') def get_message(title, recipients, body=None, html=None, attachment_name=None, attachment_type=None, attachment=None): msg = flask_mail.Message(title, recipients=recipients) if body: msg.body = body if html: msg.html = html if attachment: msg.attach(attachment_name, attachment_type, attachment.read()) return msg def get_message_from_form(form, vacancy_title): recipients = current_app.config["MAILS_TO_SEND"] kwargs = {
log.debug("Got mistletoe") except ImportError as e: # pragma: no cover try: from commonmark import commonmark log.debug("Got commonmark") except ImportError: def commonmark(msg): return msg print("No commonmark/markdown library installed. Install mistletoe" " or commonmark") __version__ = "2020.08.14" POLICY = EmailPolicy(utf8=True) CONFIG_PATH = Path("~/.wemailrc").expanduser() _parser = BytesParser(_class=EmailMessage, policy=POLICY) _header_parser = BytesHeaderParser(policy=POLICY) DEFAULT_HEADERS = {"From": "", "To": "", "Subject": ""} DISPLAY_HEADERS = ("From", "To", "CC", "Reply-to", "List-Id", "Date", "Subject") EmailTemplate = collections.namedtuple("EmailTemplate", "name,content") LOCAL_TZ = datetime.now(timezone.utc).astimezone().tzinfo class WEmailError(Exception): pass class WEmailDeliveryError(WEmailError):
import os import logging from emlx.message import EmlxMessage # For partial message reassembly import email from email.policy import EmailPolicy # Just enough to get a regular message back out without # significantly altering the message payload. minimal_policy = EmailPolicy(linesep="\r\n", refold_source="none") APPLE_MARKER = "X-Apple-Content-Length" class AMMessageRef(object): mailbox = None msgid = 0 partial = False def __repr__(self): return "<AMMessageRef msgid=%r partial=%r path=%s>" % ( self.msgid, self.partial, self.msg_path) def __init__(self, mailbox, msgid, partial=False): self.mailbox = mailbox self.msgid = msgid self.partial = partial @property def msg_dir(self):
def __init__(self, content): super().__init__() msg = message_from_bytes(content, policy=EmailPolicy(utf8=True, linesep='\r\n')) self._msg = msg logging.info('Message with subject: %s', self['Subject'])
class EMail(IDs): """Create multipart E-Mails""" __slots__ = [ 'company_id', 'mfrom', 'sender', 'receivers', 'subject', 'headers', 'content', 'charset', 'attachments', 'host', 'user', 'sender' ] TO = 0 CC = 1 BCC = 2 @staticmethod def force_encoding(charset: str, encoding: str) -> None: """Enforce an ``encoding'' for a definied ``charset'' overwriting system default behaviour""" try: charset_encoding = { 'quoted-printable': QP, 'qp': QP, 'base64': BASE64, 'b64': BASE64 }[encoding.lower()] add_charset(charset=charset.lower(), header_enc=charset_encoding, body_enc=charset_encoding) except KeyError as e: raise error(f'Invalid character set or encoding: {e}') @staticmethod def from_string(content: str) -> EmailMessage: return cast( EmailMessage, email.message_from_string(content, policy=email.policy.default)) nofold_policy = EmailPolicy(refold_source='none') @staticmethod def as_string(msg: EmailMessage, unixfrom: bool) -> str: try: return msg.as_string(unixfrom) except Exception: try: return msg.as_string(unixfrom, policy=EMail.nofold_policy) except Exception: return msg.as_string(unixfrom, policy=compat32) class Content: """Stores one part of a multipart message""" __slots__ = ['content', 'charset', 'content_type', 'related'] def __init__(self, content: str, charset: Optional[str], content_type: Optional[str]) -> None: self.content = content self.charset = charset self.content_type = content_type self.related: List[EMail.Content] = [] def set_message(self, msg: EmailMessage, charset: Optional[str]) -> None: if self.content_type is not None: msg.set_type(self.content_type) msg.set_payload( self.content, self.charset if self.charset is not None else charset) class Attachment(Content): """Stores an attachemnt as part of a multipart message""" __slots__ = ['raw_content', 'filename'] def __init__(self, raw_content: bytes, charset: Optional[str], content_type: Optional[str], filename: Optional[str]) -> None: super().__init__('', charset, content_type) self.raw_content = raw_content self.filename = filename def set_message(self, msg: EmailMessage, charset: Optional[str]) -> None: content_type = self.content_type if self.content_type is not None else 'application/octet-stream' if self.filename: content_type += f'; name="{self.filename}"' if charset is not None: content_type += f'; charset="{charset}"' msg['Content-Type'] = content_type if self.charset is not None: # msg['Content-Transfer-Encoding'] = '8bit' content = self.raw_content.decode(self.charset) else: msg['Content-Transfer-Encoding'] = 'base64' content = base64.encodestring( self.raw_content).decode('us-ascii') if self.filename: msg['Content-Description'] = self.filename msg['Content-Location'] = self.filename msg['Content-ID'] = f'<{self.filename}>' msg.set_payload(content, self.charset) def __init__(self) -> None: super().__init__() self.company_id: Optional[int] = None self.mfrom: Optional[str] = None self.sender: Optional[str] = None self.receivers: List[Tuple[int, str]] = [] self.subject: Optional[str] = None self.headers: List[str] = [] self.content: List[EMail.Content] = [] self.charset: Optional[str] = None self.attachments: List[EMail.Content] = [] try: self.host = socket.getfqdn() except Exception: self.host = fqdn pw = self.get_user() self.user = pw.pw_name if pw is not None else user if self.user and self.host: self.mfrom = self.sender = f'{self.user}@{self.host}' def set_company_id(self, company_id: Optional[int]) -> None: """Set company_id, used when signing the message""" self.company_id = company_id def set_envelope(self, mfrom: str) -> None: """Set the envelope from address""" self.mfrom = mfrom set_mfrom = set_envelope def set_sender(self, sender: str) -> None: """Set the sender of for the mail""" self.sender = sender set_from = set_sender def add_receiver(self, recv: str) -> None: """Add a receiver for the mail""" self.receivers.append((self.TO, recv)) add_to = add_receiver def add_cc(self, recv: str) -> None: """Add a carbon copy receiver for the mail""" self.receivers.append((self.CC, recv)) def add_bcc(self, recv: str) -> None: """Add a blind carbon copy receiver for the mail""" self.receivers.append((self.BCC, recv)) def reset_recipients(self) -> None: """Clears all receivers of the mail""" self.receivers.clear() def set_subject(self, subject: str) -> None: """Set the content of the subject header""" self.subject = subject def add_header(self, head: str) -> None: """Add a header""" self.headers.append(head) def reset_header(self) -> None: """Clears all definied header""" self.headers.clear() def add_content(self, content: str, charset: Optional[str], content_type: str) -> EMail.Content: """Add ``content'' (str), store it using ``charset'' and mark it of type ``content_type''""" rc = self.Content(content, charset, content_type) self.content.append(rc) return rc def reset_content(self) -> None: """Clearts all parts of the multipart message""" self.content.clear() def set_text(self, text: str, charset: Optional[str] = None) -> EMail.Content: """Add a plain ``text'' variant for the mail using ``charset''""" return self.add_content(text, charset, 'text/plain') def set_html(self, html: str, charset: Optional[str] = None) -> EMail.Content: """Add a ``html'' variant for the mail using ``charset''""" return self.add_content(html, charset, 'text/html') def set_charset(self, charset: str) -> None: """Set global ``charset'' to be used for this mail""" self.charset = charset def __content_type(self, content_type: Optional[str], filename: Optional[str], default: Optional[str]) -> Optional[str]: if content_type is None and filename is not None: content_type = mimetypes.guess_type(filename)[0] if content_type is None: content_type = default return content_type def add_text_attachment( self, content: str, charset: Optional[str] = None, content_type: Optional[str] = None, filename: Optional[str] = None, related: Optional[EMail.Content] = None) -> EMail.Attachment: """Add a textual attachment""" if charset is None: charset = 'UTF-8' content_type = self.__content_type(content_type, filename, 'text/plain') at = self.Attachment(content.encode(charset), charset, content_type, filename) if related is not None: related.related.append(at) else: self.attachments.append(at) return at def add_binary_attachment( self, raw_content: bytes, content_type: Optional[str] = None, filename: Optional[str] = None, related: Optional[EMail.Content] = None) -> EMail.Attachment: """Add a binary attachment""" content_type = self.__content_type(content_type, filename, 'application/octet-stream') at = self.Attachment(raw_content, None, content_type, filename) if related is not None: related.related.append(at) else: self.attachments.append(at) return at def add_excel_attachment( self, raw_content: bytes, filename: Optional[str], related: Optional[EMail.Content] = None) -> EMail.Attachment: """Add an excel sheet binary representation attachement""" return self.add_binary_attachment( raw_content, content_type='application/vnd.ms-excel', filename=filename, related=related) def reset_Attachment(self) -> None: """Clears all attachments""" self.attachments.clear() __name_pattern = re.compile('^([a-z][a-z0-9_-]*):', re.IGNORECASE) def __cleanup_header(self, head: str) -> Tuple[Optional[str], str]: head = head.replace('\r\n', '\n').rstrip('\n') mtch = self.__name_pattern.match(head) return (mtch.group(1).lower() if mtch else None, head) def __finalize_header(self) -> Tuple[List[str], str]: headers: List[str] = [] avail_headers: Set[str] = set() for head in self.headers: (name, header) = self.__cleanup_header(head) if name is not None and not name.startswith( 'content-') and not name in ('mime-version', ): headers.append(header) avail_headers.add(name) if not 'from' in avail_headers and self.sender: headers.append(f'From: {self.sender}') for (hid, sid) in [('to', self.TO), ('cc', self.CC)]: if not hid in avail_headers: recvs = [_r[1] for _r in self.receivers if _r[0] == sid] if recvs: headers.append('{name}: {receivers}'.format( name=hid.capitalize(), receivers=', '.join(recvs))) if not 'subject' in avail_headers and self.subject: headers.append(f'Subject: {self.subject}') charset = self.charset if self.charset is not None else 'UTF-8' nheaders = [] for header in headers: (name, value) = header.split(':', 1) try: value.encode('ascii') except UnicodeEncodeError: nheaders.append('{name}: {content}'.format( name=name, content=Header(value.strip(), charset).encode().replace('\n', ' '))) else: nheaders.append(header.replace('\n', ' ')) return (nheaders, charset) def build_mail(self) -> str: """Build the multipart mail and return it as a string""" (headers, charset) = self.__finalize_header() root = EmailMessage() for header in headers: (name, value) = header.split(':', 1) root[name] = value.strip() msgs = [] parts = [] if len(self.content) == 1: if not self.attachments: parts.append((root, self.content[0])) else: msg = EmailMessage() msgs.append(msg) root.attach(msg) parts.append((msg, self.content[0])) else: if self.content: if self.attachments: parent = EmailMessage() msgs.append(parent) root.attach(parent) else: parent = root parent.set_type('multipart/alternative') for content in self.content: msg = EmailMessage() msgs.append(msg) parts.append((msg, content)) if content.related: base_message = EmailMessage() msgs.append(base_message) base_message.set_type('multipart/related') base_message.attach(msg) for related in content.related: r = EmailMessage() msgs.append(r) parts.append((r, related)) base_message.attach(r) parent.attach(base_message) else: parent.attach(msg) for (msg, content) in parts: content.set_message(msg, charset) if self.attachments: root.set_type('multipart/related') for attachment in self.attachments: at = EmailMessage() msgs.append(at) root.attach(at) attachment.set_message(at, None) for msg in msgs: del msg['MIME-Version'] return self.sign(EMail.as_string(root, False) + '\n') def send_mail(self) -> Tuple[bool, int, str, str]: """Build and send the mail""" (status, returnCode, out, err) = (False, 0, None, None) mail = self.build_mail() mfrom: Optional[str] if self.mfrom is not None: mfrom = self.mfrom elif self.sender: mfrom = parseaddr(self.sender)[1] else: mfrom = None recvs = [parseaddr(_r[1])[1] for _r in self.receivers] sendmail = which('sendmail') if not sendmail: sendmail = '/usr/sbin/sendmail' cmd = [sendmail] if mfrom is not None: cmd += ['-f', mfrom] cmd += ['--'] + recvs pp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, errors='backslashreplace') (out, err) = pp.communicate(mail) returnCode = pp.returncode if returnCode == 0: status = True return (status, returnCode, out, err) def sign(self, message: str) -> str: return message