diff --git a/CHANGELOG.md b/CHANGELOG.md index 06c466b..02001d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog -## Nov 19th 2023 - V1.1.3 +## V1.1.5 +- Added support for plaintext file attachments +- Updated the way attachments are stored. Now it's md5 + filename + +## V1.1.4 +- Fixed crash when email contains attachment + +## V1.1.3 - Switched SMTP server to Python3 and aiosmptd - Switched PHP backend to PHP8.1 - Implemented content-id replacement with smart link to API so embedded images will now work @@ -8,7 +15,7 @@ - Removed quotes from ini settings - Made docker start script more neat -## Nov 13th 2023 - V1.0.0 +## V1.0.0 - Launch of V1.0.0 - Complete rewrite of the GUI - Breaking: New API (/rss, /json, /api) instead of old `api.php` calls diff --git a/python/mailserver3.py b/python/mailserver3.py index e66b53f..98027b2 100644 --- a/python/mailserver3.py +++ b/python/mailserver3.py @@ -6,7 +6,7 @@ import re import time import json -import uuid +import hashlib import configparser from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText @@ -48,22 +48,15 @@ async def handle_DATA(self, server, session, envelope): if part.get_content_maintype() == 'multipart': continue if part.get_content_type() == 'text/plain': - plaintext += part.get_payload() + #if it's a file + if part.get_filename() is not None: + attachments['file%d' % len(attachments)] = self.handleAttachment(part) + else: + plaintext += part.get_payload() elif part.get_content_type() == 'text/html': html += part.get_payload() else: - filename = part.get_filename() - cid = part.get('Content-ID') - if cid is not None: - cid = cid[1:-1] - elif part.get('Content-ID') is not None: - cid = part.get('Content-ID') - else: - cid = str(uuid.uuid4()) - logger.debug('Handling attachment: "%s" of type "%s" with CID "%s"',filename, part.get_content_type(), cid) - if filename is None: - filename = 'untitled' - attachments['file%d' % len(attachments)] = (filename,part.get_payload(decode=True),cid) + attachments['file%d' % len(attachments)] = self.handleAttachment(part) for em in rcpts: em = em.lower() @@ -106,15 +99,16 @@ async def handle_DATA(self, server, session, envelope): if not os.path.exists("../data/"+em+"/attachments"): os.mkdir( "../data/"+em+"/attachments", 0o755 ) attd = attachments[att] - file = open("../data/"+em+"/attachments/"+filenamebase+"-"+attd[0], 'wb') + file_id = attd[3] + file = open("../data/"+em+"/attachments/"+file_id, 'wb') file.write(attd[1]) file.close() - edata["attachments"].append(filenamebase+"-"+attd[0]) + edata["attachments"].append(file_id) edata["attachments_details"].append({ "filename":attd[0], "cid":attd[2], - "id":filenamebase+"-"+attd[0], - "download_url":URL+"/api/attachment/"+em+"/"+filenamebase+"-"+attd[0], + "id":attd[3], + "download_url":URL+"/api/attachment/"+em+"/"+file_id, "size":len(attd[1]) }) @@ -123,6 +117,22 @@ async def handle_DATA(self, server, session, envelope): json.dump(savedata, outfile) return '250 OK' + + def handleAttachment(self, part): + filename = part.get_filename() + if filename is None: + filename = 'untitled' + cid = part.get('Content-ID') + if cid is not None: + cid = cid[1:-1] + elif part.get('X-Attachment-Id') is not None: + cid = part.get('X-Attachment-Id') + else: # else create a unique id using md5 of the attachment + cid = hashlib.md5(part.get_payload(decode=True)).hexdigest() + fid = hashlib.md5(filename.encode('utf-8')).hexdigest()+filename + logger.debug('Handling attachment: "%s" (ID: "%s") of type "%s" with CID "%s"',filename, fid,part.get_content_type(), cid) + + return (filename,part.get_payload(decode=True),cid,fid) def replace_cid_with_attachment_id(self, html_content, attachments,filenamebase,email): # Replace cid references with attachment filename diff --git a/web/inc/OpenTrashmailBackend.class.php b/web/inc/OpenTrashmailBackend.class.php index 51764ab..f3b6a05 100644 --- a/web/inc/OpenTrashmailBackend.class.php +++ b/web/inc/OpenTrashmailBackend.class.php @@ -152,18 +152,12 @@ function getRawMail($email,$id,$htmlbody=false) function getAttachment($email,$attachment) { - $id = substr($attachment,0,13); - $attachment = substr($attachment,14); if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return $this->error('Invalid email address'); - else if(!is_numeric($id)) - return $this->error('Invalid id'); - else if(!emailIDExists($email,$id)) - return $this->error('Email not found'); - else if(!attachmentExists($email,$id,$attachment)) + else if(!attachmentExists($email,$attachment)) return $this->error('Attachment not found'); $dir = getDirForEmail($email); - $file = $dir.DS.'attachments'.DS.$id.'-'.$attachment; + $file = $dir.DS.'attachments'.DS.$attachment; $mime = mime_content_type($file); header('Content-Type: '.$mime); header('Content-Length: ' . filesize($file)); diff --git a/web/inc/core.php b/web/inc/core.php index 2ee1b14..89212cc 100644 --- a/web/inc/core.php +++ b/web/inc/core.php @@ -127,9 +127,9 @@ function listEmailAdresses() return $o; } -function attachmentExists($email,$id,$attachment) +function attachmentExists($email,$id,$attachment=false) { - return file_exists(getDirForEmail($email).DS.'attachments'.DS.$id.'-'.$attachment); + return file_exists(getDirForEmail($email).DS.'attachments'.DS.$id.(($attachment)?'-'.$attachment:'')); } function listAttachmentsOfMailID($email,$id)