Fix imap parsing non rfc compliant date crash (#93630)
* Fix imap parsing non rfc compliant date crash * Use parsedate_to_datetime from mail.utils
This commit is contained in:
parent
202c9071a4
commit
4c0d169cfc
3 changed files with 64 additions and 20 deletions
|
@ -5,6 +5,8 @@ import asyncio
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import email
|
import email
|
||||||
|
from email.header import decode_header, make_header
|
||||||
|
from email.utils import parseaddr, parsedate_to_datetime
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
@ -82,9 +84,9 @@ class ImapMessage:
|
||||||
"""Get the email headers."""
|
"""Get the email headers."""
|
||||||
header_base: dict[str, tuple[str,]] = {}
|
header_base: dict[str, tuple[str,]] = {}
|
||||||
for key, value in self.email_message.items():
|
for key, value in self.email_message.items():
|
||||||
header: tuple[str,] = (str(value),)
|
header_instances: tuple[str,] = (str(value),)
|
||||||
if header_base.setdefault(key, header) != header:
|
if header_base.setdefault(key, header_instances) != header_instances:
|
||||||
header_base[key] += header # type: ignore[assignment]
|
header_base[key] += header_instances # type: ignore[assignment]
|
||||||
return header_base
|
return header_base
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -94,23 +96,26 @@ class ImapMessage:
|
||||||
date_str: str | None
|
date_str: str | None
|
||||||
if (date_str := self.email_message["Date"]) is None:
|
if (date_str := self.email_message["Date"]) is None:
|
||||||
return None
|
return None
|
||||||
# In some cases a timezone or comment is added in parenthesis after the date
|
try:
|
||||||
# We want to strip that part to avoid parsing errors
|
mail_dt_tm = parsedate_to_datetime(date_str)
|
||||||
return datetime.strptime(
|
except ValueError:
|
||||||
date_str.split("(")[0].strip(), "%a, %d %b %Y %H:%M:%S %z"
|
_LOGGER.debug(
|
||||||
)
|
"Parsed date %s is not compliant with rfc2822#section-3.3", date_str
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return mail_dt_tm
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sender(self) -> str:
|
def sender(self) -> str:
|
||||||
"""Get the parsed message sender from the email."""
|
"""Get the parsed message sender from the email."""
|
||||||
return str(email.utils.parseaddr(self.email_message["From"])[1])
|
return str(parseaddr(self.email_message["From"])[1])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def subject(self) -> str:
|
def subject(self) -> str:
|
||||||
"""Decode the message subject."""
|
"""Decode the message subject."""
|
||||||
decoded_header = email.header.decode_header(self.email_message["Subject"])
|
decoded_header = decode_header(self.email_message["Subject"])
|
||||||
header = email.header.make_header(decoded_header)
|
subject_header = make_header(decoded_header)
|
||||||
return str(header)
|
return str(subject_header)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text(self) -> str:
|
def text(self) -> str:
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
|
|
||||||
DATE_HEADER1 = b"Date: Fri, 24 Mar 2023 13:52:00 +0100\r\n"
|
DATE_HEADER1 = b"Date: Fri, 24 Mar 2023 13:52:00 +0100\r\n"
|
||||||
DATE_HEADER2 = b"Date: Fri, 24 Mar 2023 13:52:00 +0100 (CET)\r\n"
|
DATE_HEADER2 = b"Date: Fri, 24 Mar 2023 13:52:00 +0100 (CET)\r\n"
|
||||||
DATE_HEADER_INVALID = b"2023-03-27T13:52:00 +0100\r\n"
|
DATE_HEADER3 = b"Date: 24 Mar 2023 13:52:00 +0100\r\n"
|
||||||
|
DATE_HEADER_INVALID1 = b"2023-03-27T13:52:00 +0100\r\n"
|
||||||
|
DATE_HEADER_INVALID2 = b"Date: 2023-03-27T13:52:00 +0100\r\n"
|
||||||
|
DATE_HEADER_INVALID3 = b"Date: Fri, 2023-03-27T13:52:00 +0100\r\n"
|
||||||
|
|
||||||
TEST_MESSAGE_HEADERS1 = (
|
TEST_MESSAGE_HEADERS1 = (
|
||||||
b"Return-Path: <john.doe@example.com>\r\nDelivered-To: notify@example.com\r\n"
|
b"Return-Path: <john.doe@example.com>\r\nDelivered-To: notify@example.com\r\n"
|
||||||
|
@ -23,7 +26,15 @@ TEST_MESSAGE_HEADERS2 = (
|
||||||
|
|
||||||
TEST_MESSAGE = TEST_MESSAGE_HEADERS1 + DATE_HEADER1 + TEST_MESSAGE_HEADERS2
|
TEST_MESSAGE = TEST_MESSAGE_HEADERS1 + DATE_HEADER1 + TEST_MESSAGE_HEADERS2
|
||||||
TEST_MESSAGE_ALT = TEST_MESSAGE_HEADERS1 + DATE_HEADER2 + TEST_MESSAGE_HEADERS2
|
TEST_MESSAGE_ALT = TEST_MESSAGE_HEADERS1 + DATE_HEADER2 + TEST_MESSAGE_HEADERS2
|
||||||
TEST_INVALID_DATE = TEST_MESSAGE_HEADERS1 + DATE_HEADER_INVALID + TEST_MESSAGE_HEADERS2
|
TEST_INVALID_DATE1 = (
|
||||||
|
TEST_MESSAGE_HEADERS1 + DATE_HEADER_INVALID1 + TEST_MESSAGE_HEADERS2
|
||||||
|
)
|
||||||
|
TEST_INVALID_DATE2 = (
|
||||||
|
TEST_MESSAGE_HEADERS1 + DATE_HEADER_INVALID2 + TEST_MESSAGE_HEADERS2
|
||||||
|
)
|
||||||
|
TEST_INVALID_DATE3 = (
|
||||||
|
TEST_MESSAGE_HEADERS1 + DATE_HEADER_INVALID3 + TEST_MESSAGE_HEADERS2
|
||||||
|
)
|
||||||
|
|
||||||
TEST_CONTENT_TEXT_BARE = b"\r\n" b"Test body\r\n" b"\r\n"
|
TEST_CONTENT_TEXT_BARE = b"\r\n" b"Test body\r\n" b"\r\n"
|
||||||
|
|
||||||
|
@ -110,13 +121,35 @@ TEST_FETCH_RESPONSE_TEXT_PLAIN_ALT = (
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_FETCH_RESPONSE_INVALID_DATE = (
|
TEST_FETCH_RESPONSE_INVALID_DATE1 = (
|
||||||
"OK",
|
"OK",
|
||||||
[
|
[
|
||||||
b"1 FETCH (BODY[] {"
|
b"1 FETCH (BODY[] {"
|
||||||
+ str(len(TEST_INVALID_DATE + TEST_CONTENT_TEXT_PLAIN)).encode("utf-8")
|
+ str(len(TEST_INVALID_DATE1 + TEST_CONTENT_TEXT_PLAIN)).encode("utf-8")
|
||||||
+ b"}",
|
+ b"}",
|
||||||
bytearray(TEST_INVALID_DATE + TEST_CONTENT_TEXT_PLAIN),
|
bytearray(TEST_INVALID_DATE1 + TEST_CONTENT_TEXT_PLAIN),
|
||||||
|
b")",
|
||||||
|
b"Fetch completed (0.0001 + 0.000 secs).",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
TEST_FETCH_RESPONSE_INVALID_DATE2 = (
|
||||||
|
"OK",
|
||||||
|
[
|
||||||
|
b"1 FETCH (BODY[] {"
|
||||||
|
+ str(len(TEST_INVALID_DATE2 + TEST_CONTENT_TEXT_PLAIN)).encode("utf-8")
|
||||||
|
+ b"}",
|
||||||
|
bytearray(TEST_INVALID_DATE2 + TEST_CONTENT_TEXT_PLAIN),
|
||||||
|
b")",
|
||||||
|
b"Fetch completed (0.0001 + 0.000 secs).",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
TEST_FETCH_RESPONSE_INVALID_DATE3 = (
|
||||||
|
"OK",
|
||||||
|
[
|
||||||
|
b"1 FETCH (BODY[] {"
|
||||||
|
+ str(len(TEST_INVALID_DATE3 + TEST_CONTENT_TEXT_PLAIN)).encode("utf-8")
|
||||||
|
+ b"}",
|
||||||
|
bytearray(TEST_INVALID_DATE3 + TEST_CONTENT_TEXT_PLAIN),
|
||||||
b")",
|
b")",
|
||||||
b"Fetch completed (0.0001 + 0.000 secs).",
|
b"Fetch completed (0.0001 + 0.000 secs).",
|
||||||
],
|
],
|
||||||
|
|
|
@ -18,7 +18,9 @@ from .const import (
|
||||||
EMPTY_SEARCH_RESPONSE,
|
EMPTY_SEARCH_RESPONSE,
|
||||||
TEST_FETCH_RESPONSE_BINARY,
|
TEST_FETCH_RESPONSE_BINARY,
|
||||||
TEST_FETCH_RESPONSE_HTML,
|
TEST_FETCH_RESPONSE_HTML,
|
||||||
TEST_FETCH_RESPONSE_INVALID_DATE,
|
TEST_FETCH_RESPONSE_INVALID_DATE1,
|
||||||
|
TEST_FETCH_RESPONSE_INVALID_DATE2,
|
||||||
|
TEST_FETCH_RESPONSE_INVALID_DATE3,
|
||||||
TEST_FETCH_RESPONSE_MULTIPART,
|
TEST_FETCH_RESPONSE_MULTIPART,
|
||||||
TEST_FETCH_RESPONSE_TEXT_BARE,
|
TEST_FETCH_RESPONSE_TEXT_BARE,
|
||||||
TEST_FETCH_RESPONSE_TEXT_OTHER,
|
TEST_FETCH_RESPONSE_TEXT_OTHER,
|
||||||
|
@ -81,7 +83,9 @@ async def test_entry_startup_fails(
|
||||||
(TEST_FETCH_RESPONSE_TEXT_BARE, True),
|
(TEST_FETCH_RESPONSE_TEXT_BARE, True),
|
||||||
(TEST_FETCH_RESPONSE_TEXT_PLAIN, True),
|
(TEST_FETCH_RESPONSE_TEXT_PLAIN, True),
|
||||||
(TEST_FETCH_RESPONSE_TEXT_PLAIN_ALT, True),
|
(TEST_FETCH_RESPONSE_TEXT_PLAIN_ALT, True),
|
||||||
(TEST_FETCH_RESPONSE_INVALID_DATE, False),
|
(TEST_FETCH_RESPONSE_INVALID_DATE1, False),
|
||||||
|
(TEST_FETCH_RESPONSE_INVALID_DATE2, False),
|
||||||
|
(TEST_FETCH_RESPONSE_INVALID_DATE3, False),
|
||||||
(TEST_FETCH_RESPONSE_TEXT_OTHER, True),
|
(TEST_FETCH_RESPONSE_TEXT_OTHER, True),
|
||||||
(TEST_FETCH_RESPONSE_HTML, True),
|
(TEST_FETCH_RESPONSE_HTML, True),
|
||||||
(TEST_FETCH_RESPONSE_MULTIPART, True),
|
(TEST_FETCH_RESPONSE_MULTIPART, True),
|
||||||
|
@ -91,7 +95,9 @@ async def test_entry_startup_fails(
|
||||||
"bare",
|
"bare",
|
||||||
"plain",
|
"plain",
|
||||||
"plain_alt",
|
"plain_alt",
|
||||||
"invalid_date",
|
"invalid_date1",
|
||||||
|
"invalid_date2",
|
||||||
|
"invalid_date3",
|
||||||
"other",
|
"other",
|
||||||
"html",
|
"html",
|
||||||
"multipart",
|
"multipart",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue