diff --git a/src/govoplan_mail/backend/sending/imap.py b/src/govoplan_mail/backend/sending/imap.py index 55e7a57..dc6cb08 100644 --- a/src/govoplan_mail/backend/sending/imap.py +++ b/src/govoplan_mail/backend/sending/imap.py @@ -685,7 +685,7 @@ def _parse_fetch_parts_with_sequence(data: list[Any] | tuple[Any, ...] | None) - def _select_readonly(client: imaplib.IMAP4, folder: str) -> tuple[int, str | None]: - typ, data = client.select(folder, readonly=True) + typ, data = client.select(_quote_mailbox_name(folder), readonly=True) if typ != "OK": raise ImapAppendError(f"IMAP folder {folder!r} could not be opened read-only: {data!r}", temporary=False) selected_count = _decode_item(data[0] if data else None).strip() @@ -1017,7 +1017,7 @@ def append_message_to_sent( client = _open_imap(imap_config) target_folder = _effective_sent_folder(config=imap_config, requested_folder=folder, client=client) internal_date = imaplib.Time2Internaldate(time.time()) - typ, data = client.append(target_folder, "\\Seen", internal_date, message_bytes) + typ, data = client.append(_quote_mailbox_name(target_folder), "\\Seen", internal_date, message_bytes) if typ != "OK": raise ImapAppendError(f"IMAP APPEND failed for folder {target_folder!r}: {data!r}", temporary=False) response = "; ".join(_decode_item(item) for item in (data or [])) or None diff --git a/tests/test_imap_parser.py b/tests/test_imap_parser.py index 152fef5..7c1f849 100644 --- a/tests/test_imap_parser.py +++ b/tests/test_imap_parser.py @@ -1,14 +1,18 @@ from __future__ import annotations import unittest +from unittest.mock import patch +from govoplan_mail.backend.config import ImapConfig from govoplan_mail.backend.sending.imap import ( _detect_sent_folder, _extract_mailbox_name, _normalize_mailbox_page, _paged_descending_sequences, _parse_fetch_sequence, + _select_readonly, _sequence_set, + append_message_to_sent, ) @@ -69,5 +73,47 @@ class ImapMessagePaginationTests(unittest.TestCase): self.assertEqual(_normalize_mailbox_page(folder=" Sent ", limit=500, offset=3), ("Sent", 100, 3)) +class ImapMailboxCommandTests(unittest.TestCase): + def test_select_quotes_mailbox_name_with_spaces(self): + class Client: + untagged_responses = {"EXISTS": [b"0"], "UIDVALIDITY": [b"1"]} + + def select(self, mailbox, readonly=False): + self.mailbox = mailbox + self.readonly = readonly + return "OK", [b"0"] + + def response(self, code): + return "OK", [b"1"] if code == "UIDVALIDITY" else [] + + client = Client() + + self.assertEqual(_select_readonly(client, "Gesendete Elemente"), (0, "1")) + self.assertEqual(client.mailbox, '"Gesendete Elemente"') + self.assertTrue(client.readonly) + + def test_append_quotes_mailbox_name_with_spaces(self): + class Client: + def append(self, mailbox, flags, date_time, message): + self.mailbox = mailbox + self.flags = flags + self.date_time = date_time + self.message = message + return "OK", [b"APPEND completed"] + + def logout(self): + return "BYE", [b"logged out"] + + client = Client() + config = ImapConfig(host="imap.example.org", username="user", password="secret", sent_folder="auto") + + with patch("govoplan_mail.backend.sending.imap._open_imap", return_value=client): + result = append_message_to_sent(b"Subject: test\r\n\r\nBody", imap_config=config, folder="Gesendete Elemente") + + self.assertEqual(client.mailbox, '"Gesendete Elemente"') + self.assertEqual(result.folder, "Gesendete Elemente") + self.assertEqual(result.bytes_appended, len(b"Subject: test\r\n\r\nBody")) + + if __name__ == "__main__": unittest.main()