From 3fcb37f2469973bc4049f4602dbeb595dbd49fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Tue, 21 Feb 2023 15:28:06 +0100 Subject: [PATCH] Reformat base64 encoded messages to shorter lines (#1575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reformat base64 encoded messages to shorter lines * Remove storing debug versions * Add example test email * Update linelength to 76 * Revert changes in pre-commit --------- Co-authored-by: Adrià Casajús --- .pre-commit-config.yaml | 1 + app/mail_sender.py | 12 +++++--- app/message_utils.py | 25 ++++++++++++++-- tests/example_emls/bad_base64format.eml | 39 +++++++++++++++++++++++++ tests/test_message_utils.py | 13 ++++++++- 5 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 tests/example_emls/bad_base64format.eml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60c43ee1..2da44625 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,3 +21,4 @@ repos: - id: djlint-jinja files: '.*\.html' entry: djlint --reformat + diff --git a/app/mail_sender.py b/app/mail_sender.py index 2705dddc..dd143d7b 100644 --- a/app/mail_sender.py +++ b/app/mail_sender.py @@ -17,7 +17,7 @@ from attr import dataclass from app import config from app.email import headers from app.log import LOG -from app.message_utils import message_to_bytes +from app.message_utils import message_to_bytes, message_format_base64_parts @dataclass @@ -173,8 +173,12 @@ class MailSender: self._save_request_to_unsent_dir(send_request) return False - def _save_request_to_unsent_dir(self, send_request: SendRequest): - file_name = f"DeliveryFail-{int(time.time())}-{uuid.uuid4()}.{SendRequest.SAVE_EXTENSION}" + def _save_request_to_unsent_dir( + self, send_request: SendRequest, prefix: str = "DeliveryFail" + ): + file_name = ( + f"{prefix}-{int(time.time())}-{uuid.uuid4()}.{SendRequest.SAVE_EXTENSION}" + ) file_path = os.path.join(config.SAVE_UNSENT_DIR, file_name) file_contents = send_request.to_bytes() with open(file_path, "wb") as fd: @@ -256,7 +260,7 @@ def sl_sendmail( send_request = SendRequest( envelope_from, envelope_to, - msg, + message_format_base64_parts(msg), mail_options, rcpt_options, is_forward, diff --git a/app/message_utils.py b/app/message_utils.py index a5a199bb..34f57d4f 100644 --- a/app/message_utils.py +++ b/app/message_utils.py @@ -1,21 +1,42 @@ +import re from email import policy from email.message import Message +from app.email import headers from app.log import LOG +# Spam assassin might flag as spam with a different line length +BASE64_LINELENGTH = 76 + def message_to_bytes(msg: Message) -> bytes: """replace Message.as_bytes() method by trying different policies""" for generator_policy in [None, policy.SMTP, policy.SMTPUTF8]: try: return msg.as_bytes(policy=generator_policy) - except: + except Exception: LOG.w("as_bytes() fails with %s policy", policy, exc_info=True) msg_string = msg.as_string() try: return msg_string.encode() - except: + except Exception: LOG.w("as_string().encode() fails", exc_info=True) return msg_string.encode(errors="replace") + + +def message_format_base64_parts(msg: Message) -> Message: + for part in msg.walk(): + if part.get( + headers.CONTENT_TRANSFER_ENCODING + ) == "base64" and part.get_content_type() in ("text/plain", "text/html"): + # Remove line breaks + body = re.sub("[\r\n]", "", part.get_payload()) + # Split in 80 column lines + chunks = [ + body[i : i + BASE64_LINELENGTH] + for i in range(0, len(body), BASE64_LINELENGTH) + ] + part.set_payload("\r\n".join(chunks)) + return msg diff --git a/tests/example_emls/bad_base64format.eml b/tests/example_emls/bad_base64format.eml new file mode 100644 index 00000000..620266a1 --- /dev/null +++ b/tests/example_emls/bad_base64format.eml @@ -0,0 +1,39 @@ +Date: Wed, 15 Feb 2023 11:35:12 +0000 +Subject: Re: test ooo +In-Reply-To: =?utf-8?q?=3CkmO5rFfb7MDX3ZZ3dFcdKRPaaBYH=5FT?= + =?utf-8?q?Bk2=5FpP=5FmcCmn0jvlb79C98upwCmq0g-3dHaTnjMjOzW?= + =?utf-8?q?ZwNvYQ8Jp=5FceX4c-IJdmpsJS4NvlFCT0EA=3D=40odd?= + =?utf-8?q?=2Equest=3E?= +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_36vJ0othnfTXaORBxaEJ0SSAbiBntoFOD5KafD1HeA" +From: leodd.vqn77@premium.sldev.ovh +To: somebody +Message-ID: + <167646093513.7.291600372246730744.9928@premium.sldev.ovh> +References: =?utf-8?q?=3CCAFuYqGjeRL3QJ4=5F=3DWxc3SM5iMmJv?= + =?utf-8?q?k+XEqD2iMYDMQkSJUz1y=3Dg=40mail=2Egmail=2Ecom?= + =?utf-8?q?biMZUb9VREY=3D=40odd=2Equest=3E_=3CRdV9zu0RmmhO?= + =?utf-8?q?A=5FGfb0ssef9WHrUBqtNCs=5F1j7DVjTCb92Rn4c7qShhi?= + =?utf-8?q?=3CkmO5rFfb7MDX3ZZ3dFcdKRPaaBYH=5FTBk2=5FpP=5Fm?= + =?utf-8?q?cCmn0jvlb79C98upwCmq0g-3dHaTnjMjOzWZwNvYQ8Jp=5F?= + =?utf-8?q?ceX4c-IJdmpsJS4NvlFCT0EA=3D=40odd=2Equest=3E?= +X-SimpleLogin-Type: Reply +X-SimpleLogin-EmailLog-ID: 9928 +X-SimpleLogin-Want-Signing: yes + +This is a multi-part message in MIME format. + +--b1_36vJ0othnfTXaORBxaEJ0SSAbiBntoFOD5KafD1HeA +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: base64 + +YWRzZmFzZGYKCi0tLS0tLS0gT3JpZ2luYWwgTWVzc2FnZSAtLS0tLS0tCk9uIF +dlZG5lc2RheSwgRmVicnVhcnkgMTV0aCwgMjAyMyBhdCAxMjoyNyBQTSwgTGVvbmFyZCBOaW1veSA8bGVvZGQudnFuNzdAcHJlbWl1bS5zbGRldi5vdmg+IHdyb3RlOgoKPiBhc2RmZHNmCj4KPiAtLS0tLS0tIE9yaWdpbmFsIE1lc3NhZ2UgLS0tLS0tLQo+IE9uIFdlZG5lc2RheSwgRmVicnVhcnkgMTV0aCwgMjAyMyBhdCAxMjoyMSBQTSwgTGVvbmFyZCBOaW1veSA8bGVvZGQudnFuNzdAcHJlbWl1bS5zbGRldi5vdmg+IHdyb3RlOgo+Cj4+IGFzZmRhc2QKPj4KPj4gLS0tLS0tLSBPcmlnaW5hbCBNZXNzYWdlIC0tLS0tLS0KPj4gT24gV2VkbmVzZGF5LCBGZWJydWFyeSAxNXRoLCAyMDIzIGF0IDEyOjE1IFBNLCBMZW9uYXJkIE5pbW95IDxsZW9kZC52cW43N0BwcmVtaXVtLnNsZGV2Lm92aD4gd3JvdGU6Cj4+Cj4+PiBhc2RmCj4+Pgo+Pj4gLS0tLS0tLSBPcmlnaW5hbCBNZXNzYWdlIC0tLS0tLS0KPj4+IE9uIFdlZG5lc2RheSwgRmVicnVhcnkgMTV0aCwgMjAyMyBhdCAxMjoxMyBQTSwgTGVvbmFyZCBOaW1veSA8bGVvZGQudnFuNzdAcHJlbWl1bS5zbGRldi5vdmg+IHdyb3RlOgo+Pj4KPj4+PiBhc2RmYXMKPj4+Pgo+Pj4+IC0tLS0tLS0gT3JpZ2luYWwgTWVzc2FnZSAtLS0tLS0tCj4+Pj4gT24gV2VkbmVzZGF5LCBGZWJydWFyeSAxNXRoLCAyMDIzIGF0IDEyOjA2IFBNLCBMZW9uYXJkIE5pbW95IDxsZW9kZC52cW43N0BwcmVtaXVtLnNsZGV2Lm92aD4gd3JvdGU6Cj4+Pj4KPj4+Pj4gYWRzZmFzZAo+Pj4+Pgo+Pj4+PiAtLS0tLS0tIE9yaWdpbmFsIE1lc3NhZ2UgLS0tLS0tLQo+Pj4+PiBPbiBXZWRuZXNkYXksIEZlYnJ1YXJ5IDE1dGgsIDIwMjMgYXQgMTI6MDUgUE0sIExlb25hcmQgTmltb3kgPGxlb2RkLnZxbjc3QHByZW1pdW0uc2xkZXYub3ZoPiB3cm90ZToKPj4+Pj4KPj4+Pj4+IHNhZGYKPj4+Pj4+Cj4+Pj4+PiAtLS0tLS0tIE9yaWdpbmFsIE1lc3NhZ2UgLS0tLS0tLQo+Pj4+Pj4gT24gV2VkbmVzZGF5LCBGZWJydWFyeSAxNXRoLCAyMDIzIGF0IDExOjUwIEFNLCBMZW9uYXJkIE5pbW95IDxsZW9kZC52cW43N0BwcmVtaXVtLnNsZGV2Lm92aD4gd3JvdGU6Cj4+Pj4+Pgo+Pj4+Pj4+IGFkc2ZhZGZhZGYKPj4+Pj4+Pgo+Pj4+Pj4+IC0tLS0tLS0gT3JpZ2luYWwgTWVzc2FnZSAtLS0tLS0tCj4+Pj4+Pj4gT24gV2VkbmVzZGF5LCBGZWJydWFyeSAxNXRoLCAyMDIzIGF0IDExOjA1IEFNLCBEZWlkYXJhIE1hbmRhcmEgLSBldmVyd2FzdGUgYXQgZ21haWwuY29tIDxldmVyd2FzdGVAZ21haWwuY29tPiB3cm90ZToKPj4+Pj4+Pgo+Pj4+Pj4+PiBhZmRmYWQKPj4+Pj4+Pj4KPj4+Pj4+Pj4gRWwgbWnDqSwgMTUgZmViIDIwMjMgYSBsYXMgMTE6MDMsIERlaWRhcmEgTWFuZGFyYSAoPGV2ZXJ3YXN0ZUBnbWFpbC5jb20+KSBlc2NyaWJpw7M6Cj4+Pj4+Pj4+Cj4+Pj4+Pj4+PiAuLy4vLy4sCj4+Pj4+Pj4+Pgo+Pj4+Pj4+Pj4gRWwgbWnDqSwgMTUgZmViIDIwMjMgYSBsYXMgMTE6MDEsIERlaWRhcmEgTWFuZGFyYSAoPGV2ZXJ3YXN0ZUBnbWFpbC5jb20+KSBlc2NyaWJpw7M6Cj4+Pj4+Pj4+Pgo+Pj4+Pj4+Pj4+IGFzZGZhc2RmCj4+Pj4+Pj4+Pj4KPj4+Pj4+Pj4+PiBFbCBtacOpLCAxNSBmZWIgMjAyMyBhIGxhcyAxMDo1OSwgRGVpZGFyYSBNYW5kYXJhICg8ZXZlcndhc3RlQGdtYWlsLmNvbT4pIGVzY3JpYmnDszoKPj4+Pj4+Pj4+Pgo+Pj4+Pj4+Pj4+PiByZXBseQ== +--b1_36vJ0othnfTXaORBxaEJ0SSAbiBntoFOD5KafD1HeA +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: base64 + +PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij5hZHNmYXNkZjxicj48L2Rpdj4NCjxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrIHByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLWVtcHR5IiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij4NCiAgICA8ZGl2IGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay11c2VyIHByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLWVtcHR5Ij4NCiAgICAgICAgDQogICAgICAgICAgICA8L2Rpdj4NCiAgICANCiAgICAgICAgICAgIDxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLXByb3RvbiBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+DQogICAgICAgIA0KICAgICAgICAgICAgPC9kaXY+DQo8L2Rpdj4NCjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OiBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNHB4OyI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfcXVvdGUiPg0KICAgICAgICAtLS0tLS0tIE9yaWdpbmFsIE1lc3NhZ2UgLS0tLS0tLTxicj4NCiAgICAgICAgT24gV2VkbmVzZGF5LCBGZWJydWFyeSAxNXRoLCAyMDIzIGF0IDEyOjI3IFBNLCBMZW9uYXJkIE5pbW95ICZsdDtsZW9kZC52cW43N0BwcmVtaXVtLnNsZGV2Lm92aCZndDsgd3JvdGU6PGJyPjxicj4NCiAgICAgICAgPGJsb2NrcXVvdGUgY2xhc3M9InByb3Rvbm1haWxfcXVvdGUiIHR5cGU9ImNpdGUiPg0KICAgICAgICAgICAgPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij5hc2RmZHNmPGJyPjwvZGl2Pg0KPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7IiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2sgcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KICAgIDxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLXVzZXIgcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KDQogICAgICAgICAgICA8L2Rpdj4NCg0KICAgICAgICAgICAgPGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJvdG9uIHByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLWVtcHR5Ij4NCg0KICAgICAgICAgICAgPC9kaXY+DQo8L2Rpdj4NCjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OiBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNHB4OyI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfcXVvdGUiPg0KICAgICAgICAtLS0tLS0tIE9yaWdpbmFsIE1lc3NhZ2UgLS0tLS0tLTxicj4NCiAgICAgICAgT24gV2VkbmVzZGF5LCBGZWJydWFyeSAxNXRoLCAyMDIzIGF0IDEyOjIxIFBNLCBMZW9uYXJkIE5pbW95ICZsdDtsZW9kZC52cW43N0BwcmVtaXVtLnNsZGV2Lm92aCZndDsgd3JvdGU6PGJyPjxicj4NCiAgICAgICAgPGJsb2NrcXVvdGUgdHlwZT0iY2l0ZSIgY2xhc3M9InByb3Rvbm1haWxfcXVvdGUiPg0KICAgICAgICAgICAgPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij5hc2ZkYXNkPGJyPjwvZGl2Pg0KPGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2sgcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiIHN0eWxlPSJmb250LWZhbWlseTogQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsiPg0KICAgIDxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLXVzZXIgcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KDQogICAgICAgICAgICA8L2Rpdj4NCg0KICAgICAgICAgICAgPGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJvdG9uIHByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLWVtcHR5Ij4NCg0KICAgICAgICAgICAgPC9kaXY+DQo8L2Rpdj4NCjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OiBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNHB4OyI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfcXVvdGUiPg0KICAgICAgICAtLS0tLS0tIE9yaWdpbmFsIE1lc3NhZ2UgLS0tLS0tLTxicj4NCiAgICAgICAgT24gV2VkbmVzZGF5LCBGZWJydWFyeSAxNXRoLCAyMDIzIGF0IDEyOjE1IFBNLCBMZW9uYXJkIE5pbW95ICZsdDtsZW9kZC52cW43N0BwcmVtaXVtLnNsZGV2Lm92aCZndDsgd3JvdGU6PGJyPjxicj4NCiAgICAgICAgPGJsb2NrcXVvdGUgY2xhc3M9InByb3Rvbm1haWxfcXVvdGUiIHR5cGU9ImNpdGUiPg0KICAgICAgICAgICAgPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij5hc2RmPGJyPjwvZGl2Pg0KPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7IiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2sgcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KICAgIDxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLXVzZXIgcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KDQogICAgICAgICAgICA8L2Rpdj4NCg0KICAgICAgICAgICAgPGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJvdG9uIHByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLWVtcHR5Ij4NCg0KICAgICAgICAgICAgPC9kaXY+DQo8L2Rpdj4NCjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OiBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNHB4OyI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfcXVvdGUiPg0KICAgICAgICAtLS0tLS0tIE9yaWdpbmFsIE1lc3NhZ2UgLS0tLS0tLTxicj4NCiAgICAgICAgT24gV2VkbmVzZGF5LCBGZWJydWFyeSAxNXRoLCAyMDIzIGF0IDEyOjEzIFBNLCBMZW9uYXJkIE5pbW95ICZsdDtsZW9kZC52cW43N0BwcmVtaXVtLnNsZGV2Lm92aCZndDsgd3JvdGU6PGJyPjxicj4NCiAgICAgICAgPGJsb2NrcXVvdGUgdHlwZT0iY2l0ZSIgY2xhc3M9InByb3Rvbm1haWxfcXVvdGUiPg0KICAgICAgICAgICAgPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij5hc2RmYXM8YnI+PC9kaXY+DQo8ZGl2IGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jayBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSIgc3R5bGU9ImZvbnQtZmFtaWx5OiBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNHB4OyI+DQogICAgPGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stdXNlciBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+DQoNCiAgICAgICAgICAgIDwvZGl2Pg0KDQogICAgICAgICAgICA8ZGl2IGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1wcm90b24gcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KDQogICAgICAgICAgICA8L2Rpdj4NCjwvZGl2Pg0KPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij48YnI+PC9kaXY+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9xdW90ZSI+DQogICAgICAgIC0tLS0tLS0gT3JpZ2luYWwgTWVzc2FnZSAtLS0tLS0tPGJyPg0KICAgICAgICBPbiBXZWRuZXNkYXksIEZlYnJ1YXJ5IDE1dGgsIDIwMjMgYXQgMTI6MDYgUE0sIExlb25hcmQgTmltb3kgJmx0O2xlb2RkLnZxbjc3QHByZW1pdW0uc2xkZXYub3ZoJmd0OyB3cm90ZTo8YnI+PGJyPg0KICAgICAgICA8YmxvY2txdW90ZSBjbGFzcz0icHJvdG9ubWFpbF9xdW90ZSIgdHlwZT0iY2l0ZSI+DQogICAgICAgICAgICA8ZGl2IHN0eWxlPSJmb250LWZhbWlseTogQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsiPmFkc2Zhc2Q8YnI+PC9kaXY+DQo8ZGl2IHN0eWxlPSJmb250LWZhbWlseTogQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsiIGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jayBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+DQogICAgPGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stdXNlciBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+DQoNCiAgICAgICAgICAgIDwvZGl2Pg0KDQogICAgICAgICAgICA8ZGl2IGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1wcm90b24gcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KDQogICAgICAgICAgICA8L2Rpdj4NCjwvZGl2Pg0KPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij48YnI+PC9kaXY+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9xdW90ZSI+DQogICAgICAgIC0tLS0tLS0gT3JpZ2luYWwgTWVzc2FnZSAtLS0tLS0tPGJyPg0KICAgICAgICBPbiBXZWRuZXNkYXksIEZlYnJ1YXJ5IDE1dGgsIDIwMjMgYXQgMTI6MDUgUE0sIExlb25hcmQgTmltb3kgJmx0O2xlb2RkLnZxbjc3QHByZW1pdW0uc2xkZXYub3ZoJmd0OyB3cm90ZTo8YnI+PGJyPg0KICAgICAgICA8YmxvY2txdW90ZSB0eXBlPSJjaXRlIiBjbGFzcz0icHJvdG9ubWFpbF9xdW90ZSI+DQogICAgICAgICAgICA8ZGl2IHN0eWxlPSJmb250LWZhbWlseTogQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsiPnNhZGY8YnI+PC9kaXY+DQo8ZGl2IGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jayBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSIgc3R5bGU9ImZvbnQtZmFtaWx5OiBBcmlhbCwgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxNHB4OyI+DQogICAgPGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stdXNlciBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+DQoNCiAgICAgICAgICAgIDwvZGl2Pg0KDQogICAgICAgICAgICA8ZGl2IGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1wcm90b24gcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KDQogICAgICAgICAgICA8L2Rpdj4NCjwvZGl2Pg0KPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij48YnI+PC9kaXY+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9xdW90ZSI+DQogICAgICAgIC0tLS0tLS0gT3JpZ2luYWwgTWVzc2FnZSAtLS0tLS0tPGJyPg0KICAgICAgICBPbiBXZWRuZXNkYXksIEZlYnJ1YXJ5IDE1dGgsIDIwMjMgYXQgMTE6NTAgQU0sIExlb25hcmQgTmltb3kgJmx0O2xlb2RkLnZxbjc3QHByZW1pdW0uc2xkZXYub3ZoJmd0OyB3cm90ZTo8YnI+PGJyPg0KICAgICAgICA8YmxvY2txdW90ZSBjbGFzcz0icHJvdG9ubWFpbF9xdW90ZSIgdHlwZT0iY2l0ZSI+DQogICAgICAgICAgICA8ZGl2IHN0eWxlPSJmb250LWZhbWlseTogQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsiPmFkc2ZhZGZhZGY8YnI+PC9kaXY+DQo8ZGl2IHN0eWxlPSJmb250LWZhbWlseTogQXJpYWwsIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsiIGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jayBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+DQogICAgPGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stdXNlciBwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+DQoNCiAgICAgICAgICAgIDwvZGl2Pg0KDQogICAgICAgICAgICA8ZGl2IGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9jay1wcm90b24gcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPg0KDQogICAgICAgICAgICA8L2Rpdj4NCjwvZGl2Pg0KPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsLCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE0cHg7Ij48YnI+PC9kaXY+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9xdW90ZSI+DQogICAgICAgIC0tLS0tLS0gT3JpZ2luYWwgTWVzc2FnZSAtLS0tLS0tPGJyPg0KICAgICAgICBPbiBXZWRuZXNkYXksIEZlYnJ1YXJ5IDE1dGgsIDIwMjMgYXQgMTE6MDUgQU0sIERlaWRhcmEgTWFuZGFyYSAtIGV2ZXJ3YXN0ZSBhdCBnbWFpbC5jb20gJmx0O2V2ZXJ3YXN0ZUBnbWFpbC5jb20mZ3Q7IHdyb3RlOjxicj48YnI+DQogICAgICAgIDxibG9ja3F1b3RlIHR5cGU9ImNpdGUiIGNsYXNzPSJwcm90b25tYWlsX3F1b3RlIj4NCiAgICAgICAgICAgIDxkaXYgZGlyPSJsdHIiPmFmZGZhZDxicj48L2Rpdj48YnI+PGRpdiBjbGFzcz0iZ21haWxfcXVvdGUiPjxkaXYgZGlyPSJsdHIiIGNsYXNzPSJnbWFpbF9hdHRyIj5FbCBtacOpLCAxNSBmZWIgMjAyMyBhIGxhcyAxMTowMywgRGVpZGFyYSBNYW5kYXJhICgmbHQ7PGEgdGFyZ2V0PSJfYmxhbmsiIHJlbD0ibm9yZWZlcnJlciBub2ZvbGxvdyBub29wZW5lciIgaHJlZj0ibWFpbHRvOmV2ZXJ3YXN0ZUBnbWFpbC5jb20iPmV2ZXJ3YXN0ZUBnbWFpbC5jb208L2E+Jmd0OykgZXNjcmliacOzOjxicj48L2Rpdj48YmxvY2txdW90ZSBjbGFzcz0iZ21haWxfcXVvdGUiIHN0eWxlPSJtYXJnaW46MHB4IDBweCAwcHggMC44ZXg7Ym9yZGVyLWxlZnQ6MXB4IHNvbGlkIHJnYigyMDQsMjA0LDIwNCk7cGFkZGluZy1sZWZ0OjFleCI+PGRpdiBkaXI9Imx0ciI+Li8uLy8uLDxicj48L2Rpdj48YnI+PGRpdiBjbGFzcz0iZ21haWxfcXVvdGUiPjxkaXYgZGlyPSJsdHIiIGNsYXNzPSJnbWFpbF9hdHRyIj5FbCBtacOpLCAxNSBmZWIgMjAyMyBhIGxhcyAxMTowMSwgRGVpZGFyYSBNYW5kYXJhICgmbHQ7PGEgcmVsPSJub3JlZmVycmVyIG5vZm9sbG93IG5vb3BlbmVyIiBocmVmPSJtYWlsdG86ZXZlcndhc3RlQGdtYWlsLmNvbSIgdGFyZ2V0PSJfYmxhbmsiPmV2ZXJ3YXN0ZUBnbWFpbC5jb208L2E+Jmd0OykgZXNjcmliacOzOjxicj48L2Rpdj48YmxvY2txdW90ZSBjbGFzcz0iZ21haWxfcXVvdGUiIHN0eWxlPSJtYXJnaW46MHB4IDBweCAwcHggMC44ZXg7Ym9yZGVyLWxlZnQ6MXB4IHNvbGlkIHJnYigyMDQsMjA0LDIwNCk7cGFkZGluZy1sZWZ0OjFleCI+PGRpdiBkaXI9Imx0ciI+YXNkZmFzZGY8YnI+PC9kaXY+PGJyPjxkaXYgY2xhc3M9ImdtYWlsX3F1b3RlIj48ZGl2IGRpcj0ibHRyIiBjbGFzcz0iZ21haWxfYXR0ciI+RWwgbWnDqSwgMTUgZmViIDIwMjMgYSBsYXMgMTA6NTksIERlaWRhcmEgTWFuZGFyYSAoJmx0OzxhIHJlbD0ibm9yZWZlcnJlciBub2ZvbGxvdyBub29wZW5lciIgaHJlZj0ibWFpbHRvOmV2ZXJ3YXN0ZUBnbWFpbC5jb20iIHRhcmdldD0iX2JsYW5rIj5ldmVyd2FzdGVAZ21haWwuY29tPC9hPiZndDspIGVzY3JpYmnDszo8YnI+PC9kaXY+PGJsb2NrcXVvdGUgY2xhc3M9ImdtYWlsX3F1b3RlIiBzdHlsZT0ibWFyZ2luOjBweCAwcHggMHB4IDAuOGV4O2JvcmRlci1sZWZ0OjFweCBzb2xpZCByZ2IoMjA0LDIwNCwyMDQpO3BhZGRpbmctbGVmdDoxZXgiPjxkaXYgZGlyPSJsdHIiPnJlcGx5PGJyPjwvZGl2Pg0KPC9ibG9ja3F1b3RlPjwvZGl2Pg0KPC9ibG9ja3F1b3RlPjwvZGl2Pg0KPC9ibG9ja3F1b3RlPjwvZGl2Pg0KDQogICAgICAgIDwvYmxvY2txdW90ZT48YnI+DQogICAgPC9kaXY+DQogICAgICAgIDwvYmxvY2txdW90ZT48YnI+DQogICAgPC9kaXY+DQogICAgICAgIDwvYmxvY2txdW90ZT48YnI+DQogICAgPC9kaXY+DQogICAgICAgIDwvYmxvY2txdW90ZT48YnI+DQogICAgPC9kaXY+DQogICAgICAgIDwvYmxvY2txdW90ZT48YnI+DQogICAgPC9kaXY+DQogICAgICAgIDwvYmxvY2txdW90ZT48YnI+DQogICAgPC9kaXY+DQogICAgICAgIDwvYmxvY2txdW90ZT48YnI+DQogICAgPC9kaXY+DQogICAgICAgIDwvYmxvY2txdW90ZT48YnI+DQogICAgPC9kaXY+ +--b1_36vJ0othnfTXaORBxaEJ0SSAbiBntoFOD5KafD1HeA-- + diff --git a/tests/test_message_utils.py b/tests/test_message_utils.py index 9c368923..88c03364 100644 --- a/tests/test_message_utils.py +++ b/tests/test_message_utils.py @@ -2,7 +2,8 @@ import email from app.email_utils import ( copy, ) -from app.message_utils import message_to_bytes +from app.message_utils import message_to_bytes, message_format_base64_parts +from tests.utils import load_eml_file def test_copy(): @@ -33,3 +34,13 @@ def test_to_bytes(): msg = email.message_from_string("éèà€") assert message_to_bytes(msg).decode() == "\néèà€" + + +def test_base64_line_breaks(): + msg = load_eml_file("bad_base64format.eml") + msg = message_format_base64_parts(msg) + for part in msg.walk(): + if part.get("content-transfer-encoding") == "base64": + body = part.get_payload() + for line in body.splitlines(): + assert len(line) <= 76