diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/compose/test/unit | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
62 files changed, 8825 insertions, 0 deletions
diff --git a/comm/mailnews/compose/test/unit/data/429891_testcase.eml b/comm/mailnews/compose/test/unit/data/429891_testcase.eml new file mode 100644 index 0000000000..b4fb4164c9 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/429891_testcase.eml @@ -0,0 +1,384 @@ +From: Invalid User <from_A@foo.invalid>
+To: =?UTF-8?B?RnLDqcOpZGxlLCBUZXN0?= <to_A@foo.invalid>
+Subject: Big email
+
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
+012345678901234567890123456789012345678901234567890123456789
diff --git a/comm/mailnews/compose/test/unit/data/binary-after-plain.txt b/comm/mailnews/compose/test/unit/data/binary-after-plain.txt Binary files differnew file mode 100644 index 0000000000..cec0697428 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/binary-after-plain.txt diff --git a/comm/mailnews/compose/test/unit/data/listexpansion.sql b/comm/mailnews/compose/test/unit/data/listexpansion.sql new file mode 100644 index 0000000000..6c4f6491be --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/listexpansion.sql @@ -0,0 +1,126 @@ +-- Address book with nested mailing lists for use in test_expandMailingLists.js. +PRAGMA user_version = 1; + +CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER); +CREATE TABLE properties (card TEXT, name TEXT, value TEXT); +CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT); +CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card)); + +INSERT INTO cards (uid, localId) VALUES + ('813155c6-924d-4751-95d0-70d8e64f16bc', 1), -- homer + ('b2cc8395-d959-45e4-9516-17457adb16fa', 2), -- marge + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 3), -- bart + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 4), -- lisa + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 5), -- maggie + ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 6), --simpson + ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 7), --marge + ('ad305609-3535-4d51-8c96-cd82d93aed46', 8), --family + ('4808121d-ebad-4564-864d-8f1149aa053b', 9), --kids + ('4926ff7a-e929-475a-8aa8-2baac994390c', 10), --parents + ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 11), --older-kids + ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 12), --bad-kids + ('34e60324-4fb6-4f10-ab1b-333b07680228', 13); --bad-younger-kids + +INSERT INTO properties (card, name, value) VALUES + ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PrimaryEmail', 'homer@example.com'), + ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PhotoType', 'generic'), + ('813155c6-924d-4751-95d0-70d8e64f16bc', 'LowercasePrimaryEmail', 'homer@example.com'), + ('813155c6-924d-4751-95d0-70d8e64f16bc', 'DisplayName', 'Simpson'), + ('813155c6-924d-4751-95d0-70d8e64f16bc', 'LastModifiedDate', '1473722922'), + ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PopularityIndex', '0'), + ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PreferMailFormat', '0'), + ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PreferDisplayName', '1'), + + ('b2cc8395-d959-45e4-9516-17457adb16fa', 'DisplayName', 'Marge'), + ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PrimaryEmail', 'marge@example.com'), + ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PhotoType', 'generic'), + ('b2cc8395-d959-45e4-9516-17457adb16fa', 'LowercasePrimaryEmail', 'marge@example.com'), + ('b2cc8395-d959-45e4-9516-17457adb16fa', 'LastModifiedDate', '1473723020'), + ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PopularityIndex', '0'), + ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PreferMailFormat', '0'), + ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PreferDisplayName', '1'), + + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PhotoType', 'generic'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PopularityIndex', '0'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PreferMailFormat', '0'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PreferDisplayName', '1'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'DisplayName', 'Bart'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PrimaryEmail', 'bart@foobar.invalid'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LowercasePrimaryEmail', 'bart@foobar.invalid'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'SecondEmail', 'bart@example.com'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LowercaseSecondEmail', 'bart@example.com'), + ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LastModifiedDate', '1473716192'), + + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PrimaryEmail', 'lisa@example.com'), + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PhotoType', 'generic'), + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'LowercasePrimaryEmail', 'lisa@example.com'), + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'DisplayName', 'lisa@example.com'), + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PopularityIndex', '0'), + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PreferMailFormat', '0'), + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'LastModifiedDate', '0'), + ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PreferDisplayName', '1'), + + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'DisplayName', 'Maggie'), + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'LastModifiedDate', '1473723047'), + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PrimaryEmail', 'maggie@example.com'), + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PhotoType', 'generic'), + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'LowercasePrimaryEmail', 'maggie@example.com'), + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PopularityIndex', '0'), + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PreferMailFormat', '0'), + ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PreferDisplayName', '1'), + + ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'DisplayName', 'simpson'), + ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'PrimaryEmail', 'simpson'), + ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'DisplayName', 'marge'), + ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'PrimaryEmail', 'marge'), + ('ad305609-3535-4d51-8c96-cd82d93aed46', 'DisplayName', 'family'), + ('ad305609-3535-4d51-8c96-cd82d93aed46', 'PrimaryEmail', 'family'), + ('4808121d-ebad-4564-864d-8f1149aa053b', 'DisplayName', 'kids'), + ('4808121d-ebad-4564-864d-8f1149aa053b', 'PrimaryEmail', 'kids'), + ('4926ff7a-e929-475a-8aa8-2baac994390c', 'DisplayName', 'parents'), + ('4926ff7a-e929-475a-8aa8-2baac994390c', 'PrimaryEmail', 'parents'), + ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 'PrimaryEmail', 'older-kids'), + ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 'DisplayName', 'older-kids'), + ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 'DisplayName', 'bad-kids'), + ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 'PrimaryEmail', 'bad-kids'), + ('34e60324-4fb6-4f10-ab1b-333b07680228', 'DisplayName', 'bad-younger-kids'), + ('34e60324-4fb6-4f10-ab1b-333b07680228', 'PrimaryEmail', 'bad-younger-kids'); + +INSERT INTO lists (uid, localId, name, nickName, description) VALUES + ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 1, 'simpson', '', ''), + ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 2, 'marge', '', 'marges own list'), + ('ad305609-3535-4d51-8c96-cd82d93aed46', 3, 'family', '', ''), + ('4808121d-ebad-4564-864d-8f1149aa053b', 4, 'kids', '', ''), + ('4926ff7a-e929-475a-8aa8-2baac994390c', 5, 'parents', '', ''), + ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 6, 'older-kids', '', ''), + ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 7, 'bad-kids', '', ''), + ('34e60324-4fb6-4f10-ab1b-333b07680228', 8, 'bad-younger-kids', '', ''); + +INSERT INTO list_cards (list, card) VALUES + -- simpson + ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer + ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge + ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '979f194e-49f2-4bbb-b364-598cdc6a7d11'), -- bart + ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '4dd13a79-b70c-4b43-bdba-bacd4e977c1b'), -- lisa + -- marge + ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer + ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge + -- family + ('ad305609-3535-4d51-8c96-cd82d93aed46', '4926ff7a-e929-475a-8aa8-2baac994390c'), -- parents + ('ad305609-3535-4d51-8c96-cd82d93aed46', '4808121d-ebad-4564-864d-8f1149aa053b'), -- kids + -- parents + ('4926ff7a-e929-475a-8aa8-2baac994390c', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer + ('4926ff7a-e929-475a-8aa8-2baac994390c', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge + ('4926ff7a-e929-475a-8aa8-2baac994390c', '4926ff7a-e929-475a-8aa8-2baac994390c'), -- parents + -- kids + ('4808121d-ebad-4564-864d-8f1149aa053b', '84fa4513-9b60-4379-ade7-1e4b48d67c84'), -- older-kids + ('4808121d-ebad-4564-864d-8f1149aa053b', 'c96402d7-1c7b-4242-a35c-b92c8ec9dfa2'), -- maggie + -- older-kids + ('84fa4513-9b60-4379-ade7-1e4b48d67c84', '4dd13a79-b70c-4b43-bdba-bacd4e977c1b'), -- lisa + ('84fa4513-9b60-4379-ade7-1e4b48d67c84', '979f194e-49f2-4bbb-b364-598cdc6a7d11'), -- bart + -- bad-kids + ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', '84fa4513-9b60-4379-ade7-1e4b48d67c84'), -- older-kids + ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', '34e60324-4fb6-4f10-ab1b-333b07680228'), -- bad-younger-kids + -- bad-younger-kids + ('34e60324-4fb6-4f10-ab1b-333b07680228', 'c96402d7-1c7b-4242-a35c-b92c8ec9dfa2'), -- maggie + ('34e60324-4fb6-4f10-ab1b-333b07680228', '8e88b9a4-2500-48e0-bcea-b1fa4eab6b72'); -- bad-kids diff --git a/comm/mailnews/compose/test/unit/data/message1.eml b/comm/mailnews/compose/test/unit/data/message1.eml new file mode 100644 index 0000000000..7913f5f262 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/message1.eml @@ -0,0 +1,7 @@ +From: from_B@foo.invalid
+To: to_B@foo.invalid
+Subject: test mail
+
+this email is in dos format because that is what the interface requires
+
+test message
diff --git a/comm/mailnews/compose/test/unit/data/shift-jis.eml b/comm/mailnews/compose/test/unit/data/shift-jis.eml new file mode 100644 index 0000000000..58f583907d --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/shift-jis.eml @@ -0,0 +1,13 @@ +To: test@example.com +From: test@example.com +Subject: ISO-2022-JP and 7bit containing =67 and hence looking like quoted-printable +Message-ID: <10a2aa17-e92f-417c-864e-575d4e371702@example.com> +Date: Tue, 3 Apr 2018 19:09:16 +0900 +User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 + Thunderbird/52.6.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=SHIFT-JIS; format=flowed +Content-Language: ja-JP +Content-Transfer-Encoding: 7bit + + diff --git a/comm/mailnews/compose/test/unit/data/test-ISO-2022-JP.txt b/comm/mailnews/compose/test/unit/data/test-ISO-2022-JP.txt new file mode 100644 index 0000000000..cd370be3f8 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/test-ISO-2022-JP.txt @@ -0,0 +1 @@ +$B%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H%F%9%H(B
diff --git a/comm/mailnews/compose/test/unit/data/test-KOI8-R.txt b/comm/mailnews/compose/test/unit/data/test-KOI8-R.txt new file mode 100644 index 0000000000..91f77cae45 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/test-KOI8-R.txt @@ -0,0 +1,2 @@ + , , , ,
+ .
diff --git a/comm/mailnews/compose/test/unit/data/test-SHIFT_JIS.txt b/comm/mailnews/compose/test/unit/data/test-SHIFT_JIS.txt new file mode 100644 index 0000000000..7a7f267540 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/test-SHIFT_JIS.txt @@ -0,0 +1 @@ +Shift_JIS̃eLXgt@CłB
diff --git a/comm/mailnews/compose/test/unit/data/test-UTF-16BE.txt b/comm/mailnews/compose/test/unit/data/test-UTF-16BE.txt Binary files differnew file mode 100644 index 0000000000..dd5fd39ed2 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/test-UTF-16BE.txt diff --git a/comm/mailnews/compose/test/unit/data/test-UTF-16LE.txt b/comm/mailnews/compose/test/unit/data/test-UTF-16LE.txt Binary files differnew file mode 100644 index 0000000000..a13a8f09e1 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/test-UTF-16LE.txt diff --git a/comm/mailnews/compose/test/unit/data/test-UTF-8.txt b/comm/mailnews/compose/test/unit/data/test-UTF-8.txt new file mode 100644 index 0000000000..b5e9df9a45 --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/test-UTF-8.txt @@ -0,0 +1 @@ +测试文件 diff --git a/comm/mailnews/compose/test/unit/data/test-windows-1252.txt b/comm/mailnews/compose/test/unit/data/test-windows-1252.txt new file mode 100644 index 0000000000..a98046517a --- /dev/null +++ b/comm/mailnews/compose/test/unit/data/test-windows-1252.txt @@ -0,0 +1,2 @@ +Buenos das - Franois a t Paris - Budapester Strae, Berlin.
+This is text in windows-1252.
diff --git a/comm/mailnews/compose/test/unit/head_compose.js b/comm/mailnews/compose/test/unit/head_compose.js new file mode 100644 index 0000000000..6f874335e5 --- /dev/null +++ b/comm/mailnews/compose/test/unit/head_compose.js @@ -0,0 +1,280 @@ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { mailTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/MailTestUtils.jsm" +); +var { localAccountUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/LocalAccountUtils.jsm" +); + +var CC = Components.Constructor; + +// WebApps.jsm called by ProxyAutoConfig (PAC) requires a valid nsIXULAppInfo. +var { getAppInfo, newAppInfo, updateAppInfo } = ChromeUtils.importESModule( + "resource://testing-common/AppInfo.sys.mjs" +); +updateAppInfo(); + +// Ensure the profile directory is set up +do_get_profile(); + +var gDEPTH = "../../../../"; + +// Import the required setup scripts. + +/* import-globals-from ../../../test/resources/abSetup.js */ +load("../../../resources/abSetup.js"); + +// Import the smtp server scripts +var { + nsMailServer, + gThreadManager, + fsDebugNone, + fsDebugAll, + fsDebugRecv, + fsDebugRecvSend, +} = ChromeUtils.import("resource://testing-common/mailnews/Maild.jsm"); +var { SmtpDaemon, SMTP_RFC2821_handler } = ChromeUtils.import( + "resource://testing-common/mailnews/Smtpd.jsm" +); +var { AuthPLAIN, AuthLOGIN, AuthCRAM } = ChromeUtils.import( + "resource://testing-common/mailnews/Auth.jsm" +); + +var gDraftFolder; + +// Setup the daemon and server +function setupServerDaemon(handler) { + if (!handler) { + handler = function (d) { + return new SMTP_RFC2821_handler(d); + }; + } + var server = new nsMailServer(handler, new SmtpDaemon()); + return server; +} + +function getBasicSmtpServer(port = 1, hostname = "localhost") { + let server = localAccountUtils.create_outgoing_server( + port, + "user", + "password", + hostname + ); + + // Override the default greeting so we get something predicitable + // in the ELHO message + Services.prefs.setCharPref("mail.smtpserver.default.hello_argument", "test"); + + return server; +} + +function getSmtpIdentity(senderName, smtpServer) { + // Set up the identity + let identity = MailServices.accounts.createIdentity(); + identity.email = senderName; + identity.smtpServerKey = smtpServer.key; + + return identity; +} + +var test; + +function do_check_transaction(real, expected) { + if (Array.isArray(real)) { + real = real.at(-1); + } + // real.them may have an extra QUIT on the end, where the stream is only + // closed after we have a chance to process it and not them. We therefore + // excise this from the list + if (real.them[real.them.length - 1] == "QUIT") { + real.them.pop(); + } + + Assert.equal(real.them.join(","), expected.join(",")); + dump("Passed test " + test + "\n"); +} + +// This listener is designed just to call OnStopCopy() when its OnStopCopy +// function is called - the rest of the functions are unneeded for a lot of +// tests (but we can't use asyncCopyListener because we need the +// nsIMsgSendListener interface as well). +var copyListener = { + // nsIMsgSendListener + onStartSending(aMsgID, aMsgSize) {}, + onProgress(aMsgID, aProgress, aProgressMax) {}, + onStatus(aMsgID, aMsg) {}, + onStopSending(aMsgID, aStatus, aMsg, aReturnFile) {}, + onGetDraftFolderURI(aMsgID, aFolderURI) {}, + onSendNotPerformed(aMsgID, aStatus) {}, + onTransportSecurityError(msgID, status, secInfo, location) {}, + + // nsIMsgCopyServiceListener + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) {}, + GetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + /* globals OnStopCopy */ + OnStopCopy(aStatus); + }, + + // QueryInterface + QueryInterface: ChromeUtils.generateQI([ + "nsIMsgSendListener", + "nsIMsgCopyServiceListener", + ]), +}; + +var progressListener = { + onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + this.resolve(gDraftFolder && mailTestUtils.firstMsgHdr(gDraftFolder)); + } + }, + + onProgressChange( + aWebProgress, + aRequest, + aCurSelfProgress, + aMaxSelfProgress, + aCurTotalProgress, + aMaxTotalProgress + ) {}, + onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {}, + onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {}, + onSecurityChange(aWebProgress, aRequest, state) {}, + onContentBlockingEvent(aWebProgress, aRequest, aEvent) {}, + + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), +}; + +function createMessage(aAttachment) { + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + fields.from = "Nobody <nobody@tinderbox.test>"; + + let attachments = []; + if (aAttachment) { + let attachment = Cc[ + "@mozilla.org/messengercompose/attachment;1" + ].createInstance(Ci.nsIMsgAttachment); + if (aAttachment instanceof Ci.nsIFile) { + attachment.url = "file://" + aAttachment.path; + attachment.contentType = "text/plain"; + attachment.name = aAttachment.leafName; + } else { + attachment.url = "data:,sometext"; + attachment.name = aAttachment; + } + attachments = [attachment]; + } + return richCreateMessage(fields, attachments); +} + +function richCreateMessage( + fields, + attachments = [], + identity = null, + account = null +) { + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + params.composeFields = fields; + + let msgCompose = MailServices.compose.initCompose(params); + if (identity === null) { + identity = getSmtpIdentity(null, getBasicSmtpServer()); + } + + let rootFolder = localAccountUtils.rootFolder; + gDraftFolder = null; + // Make sure the drafts folder is empty + try { + gDraftFolder = rootFolder.getChildNamed("Drafts"); + } catch (e) { + // we don't have to remove the folder because it doesn't exist yet + gDraftFolder = rootFolder.createLocalSubfolder("Drafts"); + } + // Clear all messages + let msgs = [...gDraftFolder.msgDatabase.enumerateMessages()]; + if (msgs.length > 0) { + gDraftFolder.deleteMessages(msgs, null, true, false, null, false); + } + + // Set attachment + fields.removeAttachments(); + for (let attachment of attachments) { + fields.addAttachment(attachment); + } + + let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance( + Ci.nsIMsgProgress + ); + let promise = new Promise((resolve, reject) => { + progressListener.resolve = resolve; + progressListener.reject = reject; + }); + progress.registerListener(progressListener); + msgCompose.sendMsg( + Ci.nsIMsgSend.nsMsgSaveAsDraft, + identity, + account ? account.key : "", + null, + progress + ); + return promise; +} + +function getAttachmentFromContent(aContent) { + function getBoundaryStringFromContent() { + let found = aContent.match( + /Content-Type: multipart\/mixed;\s+boundary="(.*?)"/ + ); + Assert.notEqual(found, null); + Assert.equal(found.length, 2); + + return found[1]; + } + + let boundary = getBoundaryStringFromContent(aContent); + let regex = new RegExp( + "\\r\\n\\r\\n--" + + boundary + + "\\r\\n" + + "([\\s\\S]*?)\\r\\n" + + "--" + + boundary + + "--", + "m" + ); + let attachments = aContent.match(regex); + Assert.notEqual(attachments, null); + Assert.equal(attachments.length, 2); + return attachments[1]; +} + +/** + * Get the body part of an MIME message. + * + * @param {string} content - The message content. + * @returns {string} + */ +function getMessageBody(content) { + let separatorIndex = content.indexOf("\r\n\r\n"); + Assert.equal(content.slice(-2), "\r\n", "Should end with a line break."); + return content.slice(separatorIndex + 4, -2); +} + +registerCleanupFunction(function () { + load(gDEPTH + "mailnews/resources/mailShutdown.js"); +}); diff --git a/comm/mailnews/compose/test/unit/test_accountKey.js b/comm/mailnews/compose/test/unit/test_accountKey.js new file mode 100644 index 0000000000..440b2ea78a --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_accountKey.js @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let MockNntpService = { + QueryInterface: ChromeUtils.generateQI(["nsINntpService"]), + postMessage(messageFile, groupNames, accountKey, urlListener, msgWindow) { + this.messageFile = messageFile; + this.groupNames = groupNames; + this.accountKey = accountKey; + }, +}; + +let MockNntpServiceFactory = { + createInstance(aIID) { + return MockNntpService; + }, +}; + +add_setup(async function () { + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory( + Components.ID("{4816dd44-fe15-4719-8cfb-a2f8ee46d787}"), + "Mock NntpService", + "@mozilla.org/messenger/nntpservice;1", + MockNntpServiceFactory + ); +}); + +/** + * Test that when accountKey is not passed to sendMessageFile, MessageSend can + * get the right account key from identity. + */ +add_task(async function testAccountKey() { + // Set up the servers. + let server = setupServerDaemon(); + localAccountUtils.loadLocalMailAccount(); + server.start(); + let smtpServer = getBasicSmtpServer(server.port); + let identity = getSmtpIdentity("from@foo.invalid", smtpServer); + let account = MailServices.accounts.createAccount(); + account.addIdentity(identity); + account.incomingServer = MailServices.accounts.createIncomingServer( + "test", + "localhost", + "pop3" + ); + + // Init nsIMsgSend and fields. + let msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + let compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + compFields.from = identity.email; + // Set the newsgroups filed so that the message will be passed to NntpService. + compFields.newsgroups = "foo.test"; + + let testFile = do_get_file("data/message1.eml"); + // Notice the second argument is accountKey. + await msgSend.sendMessageFile( + identity, + "", + compFields, + testFile, + false, + false, + Ci.nsIMsgSend.nsMsgDeliverNow, + null, + copyListener, + null, + null + ); + + // Make sure the messageFile passed to NntpService is the file we set above. + equal(MockNntpService.messageFile, testFile); + // Test accountKey passed to NntpService is correct. + equal(MockNntpService.accountKey, account.key); +}); diff --git a/comm/mailnews/compose/test/unit/test_attachment.js b/comm/mailnews/compose/test/unit/test_attachment.js new file mode 100644 index 0000000000..f0c5a4d91d --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_attachment.js @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for attachment file name. + */ + +var input0 = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmnopqrstuvwxyz{|}~" + + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" + + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" + + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf" + + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" + + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef" + + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff.txt"; + +// ascii only +var input1 = + "x!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmnopqrstuvwxyz{|}~.txt"; + +var expectedCD0 = [ + "Content-Disposition: attachment;", + " filename*0*=UTF-8''%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31;", + " filename*1*=%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F%40%41%42%43%44%45;", + " filename*2*=%46%47%48%49%4A%4B%4C%4D%4E%4F%50%51%52%53%54%55%56%57%58%59;", + " filename*3*=%5A%5B%5C%5D%5E%5F%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D;", + " filename*4*=%6E%6F%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%C2%A0;", + " filename*5*=%C2%A1%C2%A2%C2%A3%C2%A4%C2%A5%C2%A6%C2%A7%C2%A8%C2%A9%C2%AA;", + " filename*6*=%C2%AB%C2%AC%C2%AD%C2%AE%C2%AF%C2%B0%C2%B1%C2%B2%C2%B3%C2%B4;", + " filename*7*=%C2%B5%C2%B6%C2%B7%C2%B8%C2%B9%C2%BA%C2%BB%C2%BC%C2%BD%C2%BE;", + " filename*8*=%C2%BF%C3%80%C3%81%C3%82%C3%83%C3%84%C3%85%C3%86%C3%87%C3%88;", + " filename*9*=%C3%89%C3%8A%C3%8B%C3%8C%C3%8D%C3%8E%C3%8F%C3%90%C3%91%C3%92;", + " filename*10*=%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%9B;", + " filename*11*=%C3%9C%C3%9D%C3%9E%C3%9F%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4;", + " filename*12*=%C3%A5%C3%A6%C3%A7%C3%A8%C3%A9%C3%AA%C3%AB%C3%AC%C3%AD;", + " filename*13*=%C3%AE%C3%AF%C3%B0%C3%B1%C3%B2%C3%B3%C3%B4%C3%B5%C3%B6;", + " filename*14*=%C3%B7%C3%B8%C3%B9%C3%BA%C3%BB%C3%BC%C3%BD%C3%BE%C3%BF%2E;", + " filename*15*=%74%78%74", + "", +].join("\r\n"); + +var expectedCD1 = + "Content-Disposition: attachment;\r\n" + + ' filename*0="x!\\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ";\r\n' + + ' filename*1="[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.txt"\r\n'; + +var ParamFoldingPref = { + // RFC2047: 0, + RFC2047WithCRLF: 1, + RFC2231: 2, +}; + +var expectedCTList0 = { + RFC2047: + "Content-Type: text/plain; charset=UTF-8;\r\n" + + ' name="=?UTF-8?B?ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJ?=' + + "=?UTF-8?Q?JKLMNOPQRSTUVWXYZ=5b=5c=5d=5e=5f=60abcdefghijklmnopqrstuvwx?=" + + "=?UTF-8?B?eXp7fH1+wqDCocKiwqPCpMKlwqbCp8KowqnCqsKrwqzCrcKuwq/CsMKx?=" + + "=?UTF-8?B?wrLCs8K0wrXCtsK3wrjCucK6wrvCvMK9wr7Cv8OAw4HDgsODw4TDhcOG?=" + + "=?UTF-8?B?w4fDiMOJw4rDi8OMw43DjsOPw5DDkcOSw5PDlMOVw5bDl8OYw5nDmsOb?=" + + "=?UTF-8?B?w5zDncOew5/DoMOhw6LDo8Okw6XDpsOnw6jDqcOqw6vDrMOtw67Dr8Ow?=" + + '=?UTF-8?B?w7HDssOzw7TDtcO2w7fDuMO5w7rDu8O8w73DvsO/LnR4dA==?="\r\n', + + RFC2047WithCRLF: + "Content-Type: text/plain; charset=UTF-8;\r\n" + + ' name="=?UTF-8?B?ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJ?=\r\n' + + " =?UTF-8?Q?JKLMNOPQRSTUVWXYZ=5b=5c=5d=5e=5f=60abcdefghijklmnopqrstuvwx?=\r\n" + + " =?UTF-8?B?eXp7fH1+wqDCocKiwqPCpMKlwqbCp8KowqnCqsKrwqzCrcKuwq/CsMKx?=\r\n" + + " =?UTF-8?B?wrLCs8K0wrXCtsK3wrjCucK6wrvCvMK9wr7Cv8OAw4HDgsODw4TDhcOG?=\r\n" + + " =?UTF-8?B?w4fDiMOJw4rDi8OMw43DjsOPw5DDkcOSw5PDlMOVw5bDl8OYw5nDmsOb?=\r\n" + + " =?UTF-8?B?w5zDncOew5/DoMOhw6LDo8Okw6XDpsOnw6jDqcOqw6vDrMOtw67Dr8Ow?=\r\n" + + ' =?UTF-8?B?w7HDssOzw7TDtcO2w7fDuMO5w7rDu8O8w73DvsO/LnR4dA==?="\r\n', + + RFC2231: "Content-Type: text/plain; charset=UTF-8\r\n", +}; + +var expectedCTList1 = { + RFC2047: + "Content-Type: text/plain; charset=UTF-8;\r\n" + + ' name="x!\\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.txt"\r\n', + + RFC2047WithCRLF: + "Content-Type: text/plain; charset=UTF-8;\r\n" + + ' name="x!\\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.txt"\r\n', + + RFC2231: "Content-Type: text/plain; charset=UTF-8\r\n", +}; + +function checkAttachment(expectedCD, expectedCT) { + let msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + let pos = msgData.indexOf("Content-Disposition:"); + Assert.notEqual(pos, -1); + let contentDisposition = msgData.substr(pos); + pos = 0; + do { + pos = contentDisposition.indexOf("\n", pos); + Assert.notEqual(pos, -1); + pos++; + } while (contentDisposition.startsWith(" ", pos)); + contentDisposition = contentDisposition.substr(0, pos); + Assert.equal(contentDisposition, expectedCD); + + pos = msgData.indexOf("Content-Type:"); // multipart + Assert.notEqual(pos, -1); + msgData = msgData.substr(pos + 13); + pos = msgData.indexOf("Content-Type:"); // body + Assert.notEqual(pos, -1); + msgData = msgData.substr(pos + 13); + pos = msgData.indexOf("Content-Type:"); // first attachment + Assert.notEqual(pos, -1); + var contentType = msgData.substr(pos); + pos = 0; + do { + pos = contentType.indexOf("\n", pos); + Assert.notEqual(pos, -1); + pos++; + } while (contentType.startsWith(" ", pos)); + contentType = contentType.substr(0, pos); + Assert.equal(contentType.toLowerCase(), expectedCT.toLowerCase()); +} + +async function testInput0() { + for (let folding in ParamFoldingPref) { + Services.prefs.setIntPref( + "mail.strictly_mime.parm_folding", + ParamFoldingPref[folding] + ); + await createMessage(input0); + checkAttachment(expectedCD0, expectedCTList0[folding]); + } +} + +async function testInput1() { + for (let folding in ParamFoldingPref) { + Services.prefs.setIntPref( + "mail.strictly_mime.parm_folding", + ParamFoldingPref[folding] + ); + await createMessage(input1); + checkAttachment(expectedCD1, expectedCTList1[folding]); + } +} + +var tests = [testInput0, testInput1]; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + tests.forEach(x => add_task(x)); + run_next_test(); +} + +/** + * Test that the full attachment content is used to pick the CTE. + */ +add_task(async function testBinaryAfterPlainTextAttachment() { + let testFile = do_get_file("data/binary-after-plain.txt"); + await createMessage(testFile); + let msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + // If only the first few chars are used, encoding will be incorrectly 7bit. + Assert.ok( + msgData.includes( + 'Content-Disposition: attachment; filename="binary-after-plain.txt"\r\nContent-Transfer-Encoding: base64\r\n' + ) + ); +}); diff --git a/comm/mailnews/compose/test/unit/test_attachment_intl.js b/comm/mailnews/compose/test/unit/test_attachment_intl.js new file mode 100644 index 0000000000..6ce352d4df --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_attachment_intl.js @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * attachment test using non-ascii character + */ + +let nonAsciiUrl = "http://\u65e5\u672c\u8a9e.jp"; +let prettyResult = "\u65e5\u672c\u8a9e.jp"; + +function doAttachmentUrlTest() { + // handles non-ascii url in nsIMsgAttachment + + let attachment = Cc[ + "@mozilla.org/messengercompose/attachment;1" + ].createInstance(Ci.nsIMsgAttachment); + attachment.url = nonAsciiUrl; + + Assert.equal(attachment.url, nonAsciiUrl); +} + +function doPrettyNameTest() { + // handles non-ascii url in nsIMsgCompose + + let msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance( + Ci.nsIMsgCompose + ); + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + msgCompose.initialize(params); + + Assert.equal( + msgCompose.AttachmentPrettyName(nonAsciiUrl, null), + prettyResult + ); +} + +function run_test() { + doAttachmentUrlTest(); + doPrettyNameTest(); + + do_test_finished(); +} diff --git a/comm/mailnews/compose/test/unit/test_autoReply.js b/comm/mailnews/compose/test/unit/test_autoReply.js new file mode 100644 index 0000000000..a81dc7bcef --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_autoReply.js @@ -0,0 +1,254 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests messages generated by ReplyWithTemplate. + */ + +var { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm"); + +load("../../../resources/logHelper.js"); // watch for errors in the error console + +const kSender = "from@foo.invalid"; + +var gIncomingMailFile = do_get_file("../../../data/bugmail10"); // mail to reply to +// reply-filter-testmail: mail to reply to (but not really) +var gIncomingMailFile2 = do_get_file("../../../data/reply-filter-testmail"); +// mail to reply to (but not really, no from) +var gIncomingMailFile3 = do_get_file("../../../data/mail-without-from"); +var gTemplateMailFile = do_get_file("../../../data/template-latin1"); // template +var gTemplateMailFile2 = do_get_file("../../../data/template-utf8"); // template2 +var gTemplateFolder; + +var gServer; + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + gTemplateFolder = + localAccountUtils.rootFolder.createLocalSubfolder("Templates"); + + gServer = setupServerDaemon(); + gServer.start(); + + run_next_test(); +} + +add_task(async function copy_gIncomingMailFile() { + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + // Copy gIncomingMailFile into the Inbox. + MailServices.copy.copyFileMessage( + gIncomingMailFile, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + promiseCopyListener, + null + ); + await promiseCopyListener.promise; +}); + +add_task(async function copy_gIncomingMailFile2() { + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + // Copy gIncomingMailFile2 into the Inbox. + MailServices.copy.copyFileMessage( + gIncomingMailFile2, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + promiseCopyListener, + null + ); + await promiseCopyListener.promise; +}); + +add_task(async function copy_gIncomingMailFile3() { + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + // Copy gIncomingMailFile3 into the Inbox. + MailServices.copy.copyFileMessage( + gIncomingMailFile3, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + promiseCopyListener, + null + ); + await promiseCopyListener.promise; +}); + +add_task(async function copy_gTemplateMailFile() { + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + // Copy gTemplateMailFile into the Templates folder. + MailServices.copy.copyFileMessage( + gTemplateMailFile, + gTemplateFolder, + null, + true, + 0, + "", + promiseCopyListener, + null + ); + await promiseCopyListener.promise; +}); + +add_task(async function copy_gTemplateMailFile2() { + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + // Copy gTemplateMailFile2 into the Templates folder. + MailServices.copy.copyFileMessage( + gTemplateMailFile2, + gTemplateFolder, + null, + true, + 0, + "", + promiseCopyListener, + null + ); + await promiseCopyListener.promise; +}); + +// Test that a reply is NOT sent when the message is not addressed to "me". +add_task(async function testReplyingToUnaddressedFails() { + try { + await testReply(0); // mail 0 is not to us! + do_throw("Replied to a message not addressed to us!"); + } catch (e) { + if (e.result != Cr.NS_ERROR_ABORT) { + throw e; + } + // Ok! We didn't reply to the message not specifically addressed to + // us (from@foo.invalid). + } +}); + +// Test that a reply is sent when the message is addressed to "me". +add_task(async function testReplyingToAdressedWorksLatin1() { + try { + await testReply(1); // mail 1 is addressed to us, using template-latin1 + } catch (e) { + do_throw("Didn't reply properly to a message addressed to us! " + e); + } +}); + +// Test that a reply is sent when the message is addressed to "me". +add_task(async function testReplyingToAdressedWorksUTF8() { + try { + await testReply(1, 1); // mail 1 is addressed to us, template-utf8 + } catch (e) { + do_throw("Didn't reply properly to a message addressed to us! " + e); + } +}); + +// Test that a reply is NOT even tried when the message has no From. +add_task(async function testReplyingToMailWithNoFrom() { + try { + await testReply(2); // mail 2 has no From + do_throw( + "Shouldn't even have tried to reply reply to the message " + + "with no From and no Reply-To" + ); + } catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + throw e; + } + } +}); + +// Test reply with template. +async function testReply(aHrdIdx, aTemplateHdrIdx = 0) { + let smtpServer = getBasicSmtpServer(); + smtpServer.port = gServer.port; + + let identity = getSmtpIdentity(kSender, smtpServer); + localAccountUtils.msgAccount.addIdentity(identity); + + let msgHdr = mailTestUtils.getMsgHdrN(localAccountUtils.inboxFolder, aHrdIdx); + info( + "Msg#" + + aHrdIdx + + " author=" + + msgHdr.author + + ", recipients=" + + msgHdr.recipients + ); + let templateHdr = mailTestUtils.getMsgHdrN(gTemplateFolder, aTemplateHdrIdx); + + // See <method name="getTemplates"> in searchWidgets.xml + let msgTemplateUri = + gTemplateFolder.URI + + "?messageId=" + + templateHdr.messageId + + "&subject=" + + templateHdr.mime2DecodedSubject; + MailServices.compose.replyWithTemplate( + msgHdr, + msgTemplateUri, + null, + localAccountUtils.incomingServer + ); + + await TestUtils.waitForCondition(() => gServer._daemon.post); + let headers, body; + [headers, body] = MimeParser.extractHeadersAndBody(gServer._daemon.post); + Assert.ok(headers.get("Subject").startsWith("Auto: ")); + Assert.equal(headers.get("Auto-submitted"), "auto-replied"); + Assert.equal(headers.get("In-Reply-To"), "<" + msgHdr.messageId + ">"); + Assert.equal(headers.get("References"), "<" + msgHdr.messageId + ">"); + // XXX: something's wrong with how the fake server gets the data. + // The text gets converted to UTF-8 (regardless of what it is) at some point. + // Suspect a bug with how BinaryInputStream handles the strings. + if (templateHdr.charset == "windows-1252") { + // XXX: should really check for "åäö xlatin1" + if (!body.includes("åäö xlatin1")) { + // template-latin1 contains this + do_throw( + "latin1 body didn't go through! hdr msgid=" + + templateHdr.messageId + + ", msgbody=" + + body + ); + } + } else if (templateHdr.charset == "utf-8") { + // XXX: should really check for "åäö xutf8" + if (!body.includes("åäö xutf8")) { + // template-utf8 contains this + do_throw( + "utf8 body didn't go through! hdr msgid=" + + templateHdr.messageId + + ", msgbody=" + + body + ); + } + } else if (templateHdr.charset) { + do_throw( + "unexpected msg charset: " + + templateHdr.charset + + ", hdr msgid=" + + templateHdr.messageId + ); + } else { + do_throw("didn't find a msg charset! hdr msgid=" + templateHdr.messageId); + } + gServer.resetTest(); +} + +add_task(function teardown() { + // fake server cleanup + gServer.stop(); +}); diff --git a/comm/mailnews/compose/test/unit/test_bcc.js b/comm/mailnews/compose/test/unit/test_bcc.js new file mode 100644 index 0000000000..3689b920e7 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_bcc.js @@ -0,0 +1,330 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test that when bcc field is set, bcc header should not exist in the sent + * mail, but should exist in the mail copy (e.g. Sent folder). + */ + +var { PromiseUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseUtils.sys.mjs" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var gServer; +var gSentFolder; + +function cleanUpSent() { + let messages = [...gSentFolder.msgDatabase.enumerateMessages()]; + if (messages.length) { + gSentFolder.deleteMessages(messages, null, true, false, null, false); + } +} + +/** + * Load local mail account and start fake SMTP server. + */ +add_setup(async function setup() { + localAccountUtils.loadLocalMailAccount(); + gServer = setupServerDaemon(); + gServer.start(); + registerCleanupFunction(() => { + gServer.stop(); + }); + gSentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent"); +}); + +/** + * Send a msg with bcc field set, then check the sent mail doesn't contain bcc + * header, but the mail saved to the Sent folder contains bcc header. + */ +add_task(async function testBcc() { + gServer.resetTest(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer(gServer.port) + ); + + // Prepare the comp fields, including the bcc field. + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + fields.to = "Nobody <to@tinderbox.invalid>"; + fields.subject = "Test bcc"; + fields.bcc = "bcc@tinderbox.invalid"; + fields.body = "A\r\nBcc: \r\n mail body\r\n."; + + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + params.composeFields = fields; + + // Send the mail. + let msgCompose = MailServices.compose.initCompose(params); + msgCompose.type = Ci.nsIMsgCompType.New; + let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance( + Ci.nsIMsgProgress + ); + let promise = new Promise((resolve, reject) => { + progressListener.resolve = resolve; + progressListener.reject = reject; + }); + progress.registerListener(progressListener); + msgCompose.sendMsg( + Ci.nsIMsgSend.nsMsgDeliverNow, + identity, + "", + null, + progress + ); + await promise; + + let expectedBody = `\r\n\r\n${fields.body}`; + // Should not contain extra \r\n between head and body. + let notExpectedBody = `\r\n\r\n\r\n${fields.body}`; + + Assert.ok(gServer._daemon.post.includes("Subject: Test bcc")); + // Check that bcc header doesn't exist in the sent mail. + Assert.ok(!gServer._daemon.post.includes("Bcc: bcc@tinderbox.invalid")); + Assert.ok(gServer._daemon.post.includes(expectedBody)); + Assert.ok(!gServer._daemon.post.includes(notExpectedBody)); + + let msgData = mailTestUtils.loadMessageToString( + gSentFolder, + mailTestUtils.getMsgHdrN(gSentFolder, 0) + ); + Assert.ok(msgData.includes("Subject: Test bcc")); + // Check that bcc header exists in the mail copy. + Assert.ok(msgData.includes("Bcc: bcc@tinderbox.invalid")); + Assert.ok(msgData.includes(fields.body)); + Assert.ok(msgData.includes(expectedBody)); + Assert.ok(!msgData.includes(notExpectedBody)); +}); + +/** + * Test that non-utf8 eml attachment is intact after sent to a bcc recipient. + */ +add_task(async function testBccWithNonUtf8EmlAttachment() { + gServer.resetTest(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer(gServer.port) + ); + + // Prepare the comp fields, including the bcc field. + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + fields.to = "Nobody <to@tinderbox.invalid>"; + fields.subject = "Test bcc with non-utf8 eml attachment"; + fields.bcc = "bcc@tinderbox.invalid"; + + let testFile = do_get_file("data/shift-jis.eml"); + let attachment = Cc[ + "@mozilla.org/messengercompose/attachment;1" + ].createInstance(Ci.nsIMsgAttachment); + attachment.url = "file://" + testFile.path; + attachment.contentType = "message/rfc822"; + attachment.name = testFile.leafName; + fields.addAttachment(attachment); + + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + params.composeFields = fields; + + // Send the mail. + let msgCompose = MailServices.compose.initCompose(params); + msgCompose.type = Ci.nsIMsgCompType.New; + let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance( + Ci.nsIMsgProgress + ); + let promise = new Promise((resolve, reject) => { + progressListener.resolve = resolve; + progressListener.reject = reject; + }); + progress.registerListener(progressListener); + msgCompose.sendMsg( + Ci.nsIMsgSend.nsMsgDeliverNow, + identity, + "", + null, + progress + ); + await promise; + + Assert.ok( + gServer._daemon.post.includes( + "Subject: Test bcc with non-utf8 eml attachment" + ) + ); + // \x8C\xBB\x8B\xB5 is 現況 in SHIFT-JIS. + Assert.ok(gServer._daemon.post.includes("\r\n\r\n\x8C\xBB\x8B\xB5\r\n")); +}); + +add_task(async function testBccWithSendLater() { + gServer.resetTest(); + cleanUpSent(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer(gServer.port) + ); + let account = MailServices.accounts.createAccount(); + account.addIdentity(identity); + + // Prepare the comp fields, including the bcc field. + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + fields.to = "Nobody <to@tinderbox.invalid>"; + fields.subject = "Test bcc with send later"; + fields.bcc = "bcc@tinderbox.invalid"; + fields.body = "A\r\nBcc: \r\n mail body\r\n."; + + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + params.composeFields = fields; + + // Queue the mail to send later. + let msgCompose = MailServices.compose.initCompose(params); + msgCompose.type = Ci.nsIMsgCompType.New; + let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance( + Ci.nsIMsgProgress + ); + let promise = new Promise((resolve, reject) => { + progressListener.resolve = resolve; + progressListener.reject = reject; + }); + progress.registerListener(progressListener); + msgCompose.sendMsg( + Ci.nsIMsgSend.nsMsgQueueForLater, + identity, + "", + null, + progress + ); + await promise; + + let onStopSendingPromise = PromiseUtils.defer(); + let msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService( + Ci.nsIMsgSendLater + ); + let sendLaterListener = { + onStartSending() {}, + onMessageStartSending() {}, + onMessageSendProgress() {}, + onMessageSendError() {}, + onStopSending() { + let expectedBody = `\r\n\r\n${fields.body}`; + // Should not contain extra \r\n between head and body. + let notExpectedBody = `\r\n\r\n\r\n${fields.body}`; + + Assert.ok(gServer._daemon.post.includes(`Subject: ${fields.subject}`)); + // Check that bcc header doesn't exist in the sent mail. + Assert.ok(!gServer._daemon.post.includes("Bcc: bcc@tinderbox.invalid")); + Assert.ok(gServer._daemon.post.includes(expectedBody)); + Assert.ok(!gServer._daemon.post.includes(notExpectedBody)); + + let msgData = mailTestUtils.loadMessageToString( + gSentFolder, + mailTestUtils.getMsgHdrN(gSentFolder, 0) + ); + Assert.ok(msgData.includes(`Subject: ${fields.subject}`)); + // Check that bcc header exists in the mail copy. + Assert.ok(msgData.includes("Bcc: bcc@tinderbox.invalid")); + Assert.ok(msgData.includes(fields.body)); + Assert.ok(msgData.includes(expectedBody)); + Assert.ok(!msgData.includes(notExpectedBody)); + + msgSendLater.removeListener(sendLaterListener); + onStopSendingPromise.resolve(); + }, + }; + + msgSendLater.addListener(sendLaterListener); + + // Actually send the message. + msgSendLater.sendUnsentMessages(identity); + await onStopSendingPromise.promise; +}); + +/** + * Test that sending bcc only message from Outbox works. With a bcc only + * message, nsMsgSendLater passes `To: undisclosed-recipients: ;` to + * SmtpService, but it should not be sent to the SMTP server. + */ +add_task(async function testBccOnlyWithSendLater() { + gServer.resetTest(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer(gServer.port) + ); + let account = MailServices.accounts.createAccount(); + account.addIdentity(identity); + + // Prepare the comp fields, including the bcc field. + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + fields.subject = "Test bcc only with send later"; + fields.bcc = "bcc@tinderbox.invalid"; + fields.body = "A\r\nBcc: \r\n mail body\r\n."; + + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + params.composeFields = fields; + + // Queue the mail to send later. + let msgCompose = MailServices.compose.initCompose(params); + msgCompose.type = Ci.nsIMsgCompType.New; + let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance( + Ci.nsIMsgProgress + ); + let promise = new Promise((resolve, reject) => { + progressListener.resolve = resolve; + progressListener.reject = reject; + }); + progress.registerListener(progressListener); + msgCompose.sendMsg( + Ci.nsIMsgSend.nsMsgQueueForLater, + identity, + "", + null, + progress + ); + await promise; + + let onStopSendingPromise = PromiseUtils.defer(); + let msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService( + Ci.nsIMsgSendLater + ); + let sendLaterListener = { + onStartSending() {}, + onMessageStartSending() {}, + onMessageSendProgress() {}, + onMessageSendError() {}, + onStopSending() { + // Should not include RCPT TO:<undisclosed-recipients: ;> + do_check_transaction(gServer.playTransaction(), [ + "EHLO test", + `MAIL FROM:<from@tinderbox.invalid> BODY=8BITMIME SIZE=${gServer._daemon.post.length}`, + "RCPT TO:<bcc@tinderbox.invalid>", + "DATA", + ]); + + msgSendLater.removeListener(sendLaterListener); + onStopSendingPromise.resolve(); + }, + }; + + msgSendLater.addListener(sendLaterListener); + + // Actually send the message. + msgSendLater.sendUnsentMessages(identity); + await onStopSendingPromise.promise; +}); diff --git a/comm/mailnews/compose/test/unit/test_bug155172.js b/comm/mailnews/compose/test/unit/test_bug155172.js new file mode 100644 index 0000000000..06c14416a7 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_bug155172.js @@ -0,0 +1,140 @@ +/** + * Authentication tests for SMTP. + */ + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/alertTestUtils.js"); +load("../../../resources/passwordStorage.js"); + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var gNewPassword = null; + +// for alertTestUtils.js +function confirmExPS( + parent, + aDialogTitle, + aText, + aButtonFlags, + aButton0Title, + aButton1Title, + aButton2Title, + aCheckMsg, + aCheckState +) { + // Just return 2 which will is pressing button 2 - enter a new password. + return 2; +} + +function promptPasswordPS( + aParent, + aDialogTitle, + aText, + aPassword, + aCheckMsg, + aCheckState +) { + aPassword.value = gNewPassword; + return true; +} + +var server; + +var kIdentityMail = "identity@foo.invalid"; +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; +var kUsername = "test.smtp@fakeserver"; +// kPasswordSaved is the one defined in signons-smtp.json, the other one +// is intentionally wrong. +var kPasswordWrong = "wrong"; +var kPasswordSaved = "smtptest"; + +add_task(async function () { + registerAlertTestUtils(); + + function createHandler(d) { + var handler = new SMTP_RFC2821_handler(d); + // Username needs to match the login information stored in the signons json + // file. + handler.kUsername = kUsername; + handler.kPassword = kPasswordWrong; + handler.kAuthRequired = true; + handler.kAuthSchemes = ["PLAIN", "LOGIN"]; // make match expected transaction below + return handler; + } + + server = setupServerDaemon(createHandler); + server.setDebugLevel(fsDebugAll); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-smtp.json"); + + // Test file + var testFile = do_get_file("data/message1.eml"); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // Start the fake SMTP server + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kIdentityMail, smtpServer); + + // This time with auth + test = "Auth sendMailMessage"; + + smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext; + smtpServer.socketType = Ci.nsMsgSocketType.plain; + smtpServer.username = kUsername; + + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + + // Set the new password for when we get a prompt + gNewPassword = kPasswordWrong; + + await urlListener.promise; + + var transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPasswordSaved), + "AUTH LOGIN", + "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPasswordWrong), + "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159", + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +}); diff --git a/comm/mailnews/compose/test/unit/test_bug474774.js b/comm/mailnews/compose/test/unit/test_bug474774.js new file mode 100644 index 0000000000..ba0c20667c --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_bug474774.js @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Tests bug 474774 - assertions when saving send later and when sending with + * FCC switched off. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var server; +var smtpServer; +var originalData; +var finished = false; +var identity = null; + +var testFile = do_get_file("data/429891_testcase.eml"); + +var kTestFileSender = "from_A@foo.invalid"; +var kTestFileRecipient = "to_A@foo.invalid"; + +var kIdentityMail = "identity@foo.invalid"; + +var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService( + Ci.nsIMsgSendLater +); + +// This listener handles the post-sending of the actual message and checks the +// sequence and ensures the data is correct. +function msll() {} + +msll.prototype = { + _initialTotal: 0, + + // nsIMsgSendLaterListener + onStartSending(aTotalMessageCount) { + this._initialTotal = 1; + Assert.equal(msgSendLater.sendingMessages, true); + }, + onMessageStartSending( + aCurrentMessage, + aTotalMessageCount, + aMessageHeader, + aIdentity + ) {}, + onMessageSendProgress( + aCurrentMessage, + aTotalMessageCount, + aMessageSendPercent, + aMessageCopyPercent + ) { + // XXX Enable this function + }, + onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) { + do_throw( + "onMessageSendError should not have been called, status: " + aStatus + ); + }, + onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) { + print("msll onStopSending\n"); + try { + Assert.equal(aSuccessful, 1); + Assert.equal(aStatus, 0); + Assert.equal(aTotalTried, 1); + Assert.equal(this._initialTotal, 1); + Assert.equal(msgSendLater.sendingMessages, false); + + do_check_transaction(server.playTransaction(), [ + "EHLO test", + "MAIL FROM:<" + + kTestFileSender + + "> BODY=8BITMIME SIZE=" + + originalData.length, + "RCPT TO:<" + kTestFileRecipient + ">", + "DATA", + ]); + + // Compare data file to what the server received + Assert.equal(originalData, server._daemon.post); + + // Now wait till the copy is finished for the sent message + do_test_pending(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } + do_test_finished(); + }, +}; + +/* exported OnStopCopy */ +// for head_compose.js +function OnStopCopy(aStatus) { + do_test_finished(); + + try { + Assert.equal(aStatus, 0); + + // Check this is false before we start sending + Assert.equal(msgSendLater.sendingMessages, false); + + let folder = msgSendLater.getUnsentMessagesFolder(identity); + + // Check we have a message in the unsent message folder + Assert.equal(folder.getTotalMessages(false), 1); + + // Now do a comparison of what is in the sent mail folder + let msgData = mailTestUtils.loadMessageToString( + folder, + mailTestUtils.firstMsgHdr(folder) + ); + + // Skip the headers etc that mailnews adds + var pos = msgData.indexOf("From:"); + Assert.notEqual(pos, -1); + + msgData = msgData.substr(pos); + + // Check the data is matching. + Assert.equal(originalData, msgData); + + do_test_pending(); + sendMessageLater(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + finished = true; + } +} + +// This function does the actual send later +function sendMessageLater() { + do_test_finished(); + + // Set up the SMTP server. + server = setupServerDaemon(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // Start the fake SMTP server + server.start(); + smtpServer.port = server.port; + + // A test to check that we are sending files correctly, including checking + // what the server receives and what we output. + test = "sendMessageLater"; + + var messageListener = new msll(); + + msgSendLater.addListener(messageListener); + + // Send the unsent message + msgSendLater.sendUnsentMessages(identity); + + server.performTest(); + + do_timeout(10000, function () { + if (!finished) { + do_throw("Notifications of message send/copy not received"); + } + }); + + do_test_pending(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +add_task(async function run_the_test() { + // Test file - for bug 429891 + originalData = await IOUtils.readUTF8(testFile.path); + + // Ensure we have a local mail account, an normal account and appropriate + // servers and identities. + localAccountUtils.loadLocalMailAccount(); + + MailServices.accounts.setSpecialFolders(); + + let account = MailServices.accounts.createAccount(); + let incomingServer = MailServices.accounts.createIncomingServer( + "test", + "localhost", + "pop3" + ); + + smtpServer = getBasicSmtpServer(0); + identity = getSmtpIdentity(kIdentityMail, smtpServer); + + account.addIdentity(identity); + account.defaultIdentity = identity; + account.incomingServer = incomingServer; + MailServices.accounts.defaultAccount = account; + + localAccountUtils.rootFolder.createLocalSubfolder("Sent"); + + identity.doFcc = false; + + // Now prepare to actually "send" the message later, i.e. dump it in the + // unsent messages folder. + + var compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + // Setting the compFields sender and recipient to any value is required to + // survive mime_sanity_check_fields in nsMsgCompUtils.cpp. + // Sender and recipient are required for sendMessageFile but SMTP + // transaction values will be used directly from mail body. + compFields.from = "irrelevant@foo.invalid"; + compFields.to = "irrelevant@foo.invalid"; + + var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + + msgSend.sendMessageFile( + identity, + "", + compFields, + testFile, + false, + false, + Ci.nsIMsgSend.nsMsgQueueForLater, + null, + copyListener, + null, + null + ); + + // Now we wait till we get copy notification of completion. + do_test_pending(); +}); diff --git a/comm/mailnews/compose/test/unit/test_createAndSendMessage.js b/comm/mailnews/compose/test/unit/test_createAndSendMessage.js new file mode 100644 index 0000000000..41daecf2cc --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_createAndSendMessage.js @@ -0,0 +1,170 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test createAndSendMessage creates a mail file when not using the editor. + */ + +var server; +var sentFolder; +const originalData = "createAndSendMessage utf-8 test åäöÅÄÖ"; +// This is the originalData converted to a byte string. +const expectedData = "createAndSendMessage utf-8 test åäöÃ\x85Ã\x84Ã\x96"; +const expectedContentTypeHeaders = + "Content-Type: text/plain; charset=UTF-8; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\n"; +var finished = false; + +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; + +function checkData(msgData) { + // Skip the headers etc that mailnews adds + var pos = msgData.indexOf("Content-Type:"); + Assert.notEqual(pos, -1); + + msgData = msgData.substr(pos); + + Assert.equal(msgData, expectedContentTypeHeaders + expectedData + "\r\n"); +} + +function MessageListener() {} + +MessageListener.prototype = { + // nsIMsgSendListener + onStartSending(aMsgID, aMsgSize) {}, + onProgress(aMsgID, aProgress, aProgressMax) {}, + onStatus(aMsgID, aMsg) {}, + onStopSending(aMsgID, aStatus, aMsg, aReturnFile) { + try { + Assert.equal(aStatus, 0); + + // Compare data file to what the server received + checkData(server._daemon.post); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(false); + } + } + }, + onGetDraftFolderURI(aMsgID, aFolderURI) {}, + onSendNotPerformed(aMsgID, aStatus) {}, + onTransportSecurityError(msgID, status, secInfo, location) {}, + + // nsIMsgCopyServiceListener + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) {}, + GetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + Assert.equal(aStatus, 0); + try { + // Now do a comparison of what is in the sent mail folder + let msgData = mailTestUtils.loadMessageToString( + sentFolder, + mailTestUtils.firstMsgHdr(sentFolder) + ); + + checkData(msgData); + } catch (e) { + do_throw(e); + } finally { + finished = true; + do_test_finished(); + } + }, + + // QueryInterface + QueryInterface: ChromeUtils.generateQI([ + "nsIMsgSendListener", + "nsIMsgCopyServiceListener", + ]), +}; + +/** + * Call createAndSendMessage, expect onStopSending to be called. + */ +add_task(async function testCreateAndSendMessage() { + server = setupServerDaemon(); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + MailServices.accounts.setSpecialFolders(); + + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kSender, smtpServer); + + sentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent"); + + Assert.equal(identity.doFcc, true); + + var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // A test to check that we are sending files correctly, including checking + // what the server receives and what we output. + test = "sendMessageFile"; + + // Msg Comp Fields + + var compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + compFields.from = identity.email; + compFields.to = kTo; + + var messageListener = new MessageListener(); + + msgSend.createAndSendMessage( + null, + identity, + "", + compFields, + false, + false, + Ci.nsIMsgSend.nsMsgDeliverNow, + null, + "text/plain", + // The following parameter is the message body, test that utf-8 is handled + // correctly. + originalData, + null, + null, + messageListener, + null, + null, + Ci.nsIMsgCompType.New + ); + + server.performTest(); + + do_timeout(10000, function () { + if (!finished) { + do_throw("Notifications of message send/copy not received"); + } + }); + + do_test_pending(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +}); diff --git a/comm/mailnews/compose/test/unit/test_createRFC822Message.js b/comm/mailnews/compose/test/unit/test_createRFC822Message.js new file mode 100644 index 0000000000..9502031484 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_createRFC822Message.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test createRFC822Message creates a mail file. + */ + +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +let customSendListener = { + ...copyListener, + OnStopCopy() {}, + + /** + * Test a mail file is created and has correct content. + */ + async onStopSending(msgId, status, msg, returnFile) { + ok(returnFile.exists(), "createRFC822Message should create a mail file"); + let content = await IOUtils.read(returnFile.path); + content = String.fromCharCode(...content); + ok( + content.includes("Subject: Test createRFC822Message\r\n"), + "Mail file should contain correct subject line" + ); + ok( + content.includes( + "createRFC822Message is used by nsImportService \xe4\xe9" + ), + "Mail file should contain correct body" + ); + do_test_finished(); + }, +}; + +/** + * Call createRFC822Message, expect onStopSending to be called. + */ +add_task(async function testCreateRFC822Message() { + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + fields.from = "Somebody <somebody@tinderbox.invalid>"; + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Test createRFC822Message"; + + let msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + msgSend.createRFC822Message( + identity, + fields, + "text/plain", + // The following parameter is the message body that can contain arbitrary + // binary data, let's try some windows-1252 data (äé). + "createRFC822Message is used by nsImportService \xe4\xe9", + true, // isDraft + [], + [], + customSendListener + ); + do_test_pending(); +}); diff --git a/comm/mailnews/compose/test/unit/test_detectAttachmentCharset.js b/comm/mailnews/compose/test/unit/test_detectAttachmentCharset.js new file mode 100644 index 0000000000..27e879d018 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_detectAttachmentCharset.js @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for auto-detecting attachment file charset. + */ + +function checkAttachmentCharset(expectedCharset) { + let msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + let attachmentData = getAttachmentFromContent(msgData); + + Assert.equal(expectedCharset, getContentCharset(attachmentData)); +} + +function getContentCharset(aContent) { + let found = aContent.match(/^Content-Type: text\/plain; charset=(.*?);/); + if (found) { + Assert.equal(found.length, 2); + return found[1]; + } + return null; +} + +async function testUTF8() { + await createMessage(do_get_file("data/test-UTF-8.txt")); + checkAttachmentCharset("UTF-8"); +} + +async function testUTF16BE() { + await createMessage(do_get_file("data/test-UTF-16BE.txt")); + checkAttachmentCharset("UTF-16BE"); +} + +async function testUTF16LE() { + await createMessage(do_get_file("data/test-UTF-16LE.txt")); + checkAttachmentCharset("UTF-16LE"); +} + +async function testShiftJIS() { + await createMessage(do_get_file("data/test-SHIFT_JIS.txt")); + checkAttachmentCharset("Shift_JIS"); +} + +async function testISO2022JP() { + await createMessage(do_get_file("data/test-ISO-2022-JP.txt")); + checkAttachmentCharset("ISO-2022-JP"); +} + +async function testKOI8R() { + // NOTE: KOI8-R is detected as KOI8-U which is a superset covering both + // Russian and Ukrainian (a few box-drawing characters are repurposed). + await createMessage(do_get_file("data/test-KOI8-R.txt")); + checkAttachmentCharset("KOI8-U"); +} + +async function testWindows1252() { + await createMessage(do_get_file("data/test-windows-1252.txt")); + checkAttachmentCharset("windows-1252"); +} + +var tests = [ + testUTF8, + testUTF16BE, + testUTF16LE, + testShiftJIS, + testISO2022JP, + testKOI8R, + testWindows1252, +]; + +function run_test() { + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + Services.prefs.setIntPref("mail.strictly_mime.parm_folding", 0); + + tests.forEach(x => add_task(x)); + run_next_test(); +} diff --git a/comm/mailnews/compose/test/unit/test_expandMailingLists.js b/comm/mailnews/compose/test/unit/test_expandMailingLists.js new file mode 100644 index 0000000000..aa5998196f --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_expandMailingLists.js @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/** + * Tests nsMsgCompose expandMailingLists. + */ + +var MsgComposeContractID = "@mozilla.org/messengercompose/compose;1"; +var MsgComposeParamsContractID = + "@mozilla.org/messengercompose/composeparams;1"; +var MsgComposeFieldsContractID = + "@mozilla.org/messengercompose/composefields;1"; +var nsIMsgCompose = Ci.nsIMsgCompose; +var nsIMsgComposeParams = Ci.nsIMsgComposeParams; +var nsIMsgCompFields = Ci.nsIMsgCompFields; + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +/** + * Helper to check population worked as expected. + * + * @param {string} aTo - Text in the To field. + * @param {string} aCheckTo - The expected To addresses (after possible list population). + */ +function checkPopulate(aTo, aCheckTo) { + let msgCompose = Cc[MsgComposeContractID].createInstance(nsIMsgCompose); + + // Set up some basic fields for compose. + let fields = Cc[MsgComposeFieldsContractID].createInstance(nsIMsgCompFields); + + fields.to = aTo; + + // Set up some params + let params = + Cc[MsgComposeParamsContractID].createInstance(nsIMsgComposeParams); + + params.composeFields = fields; + + msgCompose.initialize(params); + + msgCompose.expandMailingLists(); + equal(fields.to, aCheckTo); +} + +function run_test() { + loadABFile("data/listexpansion", kPABData.fileName); + + // XXX Getting all directories ensures we create all ABs because mailing + // lists need help initialising themselves + MailServices.ab.directories; + + // Test expansion of list with no description. + checkPopulate( + "simpson <simpson>", + 'Simpson <homer@example.com>, Marge <marge@example.com>, Bart <bart@foobar.invalid>, "lisa@example.com" <lisa@example.com>' + ); + + // Test expansion fo list with description. + checkPopulate( + "marge <marges own list>", + "Simpson <homer@example.com>, Marge <marge@example.com>" + ); + + // Special tests for bug 1287726: Lists in list. This is what the data looks like: + // 1) family (list) = parents (list) + kids (list). + // 2) parents (list) = homer + marge + parents (list recursion). + // 3) kids (list) = older-kids (list) + maggie. + // 4) older-kids (list) = bart + lisa. + // 5) bad-kids (list) = older-kids + bad-younger-kids (list). + // 6) bad-younger-kids (list) = maggie + bad-kids (list recursion). + checkPopulate( + "family <family>", + "Simpson <homer@example.com>, Marge <marge@example.com>, " + + '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>, Maggie <maggie@example.com>' + ); + checkPopulate( + "parents <parents>", + "Simpson <homer@example.com>, Marge <marge@example.com>" + ); + checkPopulate( + "kids <kids>", + '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>, ' + + "Maggie <maggie@example.com>" + ); + checkPopulate( + "older-kids <older-kids>", + '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>' + ); + checkPopulate( + "bad-kids <bad-kids>", + '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>, ' + + "Maggie <maggie@example.com>" + ); + checkPopulate( + "bad-younger-kids <bad-younger-kids>", + "Maggie <maggie@example.com>, " + + '"lisa@example.com" <lisa@example.com>, Bart <bart@foobar.invalid>' + ); + + // Test we don't mistake an email address for a list, with a few variations. + checkPopulate("Simpson <homer@example.com>", "Simpson <homer@example.com>"); + checkPopulate("simpson <homer@example.com>", "simpson <homer@example.com>"); + checkPopulate( + "simpson <homer@not-in-ab.invalid>", + "simpson <homer@not-in-ab.invalid>" + ); + + checkPopulate("Marge <marge@example.com>", "Marge <marge@example.com>"); + checkPopulate("marge <marge@example.com>", "marge <marge@example.com>"); + checkPopulate( + "marge <marge@not-in-ab.invalid>", + "marge <marge@not-in-ab.invalid>" + ); +} diff --git a/comm/mailnews/compose/test/unit/test_fcc2.js b/comm/mailnews/compose/test/unit/test_fcc2.js new file mode 100644 index 0000000000..e7f8d7aadf --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_fcc2.js @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test that when fcc2 field is set, the mail is copied to the fcc2 folder. + */ + +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +let fcc2Folder; + +add_setup(async function () { + localAccountUtils.loadLocalMailAccount(); + fcc2Folder = localAccountUtils.rootFolder.createLocalSubfolder("fcc2"); +}); + +/** + * Send a message with the fcc2 field set, then check the message in the fcc2 + * folder. + */ +add_task(async function testFcc2() { + let CompFields = CC( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields + ); + let fields = new CompFields(); + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Test fcc2"; + fields.fcc2 = fcc2Folder.URI; + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + await richCreateMessage(fields, [], identity); + + // Check the message shows up correctly in the fcc2 folder. + let msgData = mailTestUtils.loadMessageToString( + fcc2Folder, + mailTestUtils.firstMsgHdr(fcc2Folder) + ); + Assert.ok(msgData.includes("Subject: Test fcc2")); +}); + +add_task(async function cleanup() { + fcc2Folder.deleteSelf(null); +}); diff --git a/comm/mailnews/compose/test/unit/test_fccReply.js b/comm/mailnews/compose/test/unit/test_fccReply.js new file mode 100644 index 0000000000..b7e44bce17 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_fccReply.js @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test that when nsIMsgIdentity.fccReplyFollowsParent is true, the reply mail + * is copied to the same folder as the original mail. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); +var { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +var gServer; + +/** + * Send a reply to originalMsgURI. + */ +async function sendReply(identity, fields, originalMsgURI, compType) { + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + params.composeFields = fields; + params.originalMsgURI = originalMsgURI; + let msgCompose = MailServices.compose.initCompose(params); + msgCompose.type = compType; + let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance( + Ci.nsIMsgProgress + ); + let promise = new Promise((resolve, reject) => { + progressListener.resolve = resolve; + progressListener.reject = reject; + }); + progress.registerListener(progressListener); + msgCompose.sendMsg( + Ci.nsIMsgSend.nsMsgDeliverNow, + identity, + "", + null, + progress + ); + return promise; +} + +/** + * Load local mail account and start fake SMTP server. + */ +add_setup(function () { + localAccountUtils.loadLocalMailAccount(); + gServer = setupServerDaemon(); + gServer.start(); + registerCleanupFunction(() => { + gServer.stop(); + }); +}); + +/** + * With fccReplyFollowsParent enabled, send a few replies then check the replies + * exists in the Inbox folder. + */ +add_task(async function testFccReply() { + // Turn on fccReplyFollowsParent. + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer(gServer.port) + ); + identity.fccReplyFollowsParent = true; + + // Copy a test mail into the Inbox. + let file = do_get_file("data/message1.eml"); // mail to reply to + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyFileMessage( + file, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + promiseCopyListener, + null + ); + await promiseCopyListener.promise; + + let CompFields = CC( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields + ); + let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder); + let originalMsgURI = msgHdr.folder.getUriForMsg(msgHdr); + + // Test nsIMsgCompFields.Reply. + let fields = new CompFields(); + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Test fcc reply"; + await sendReply(identity, fields, originalMsgURI, Ci.nsIMsgCompType.Reply); + await TestUtils.waitForCondition(() => gServer._daemon.post); + let msgData = mailTestUtils.loadMessageToString( + localAccountUtils.inboxFolder, + mailTestUtils.getMsgHdrN(localAccountUtils.inboxFolder, 1) + ); + Assert.ok(msgData.includes("Subject: Test fcc reply")); + + // Test nsIMsgCompFields.ReplyToGroup. + gServer.resetTest(); + fields.subject = "Test fccReplyToGroup"; + await sendReply( + identity, + fields, + originalMsgURI, + Ci.nsIMsgCompType.ReplyToGroup + ); + await TestUtils.waitForCondition(() => gServer._daemon.post); + msgData = mailTestUtils.loadMessageToString( + localAccountUtils.inboxFolder, + mailTestUtils.getMsgHdrN(localAccountUtils.inboxFolder, 2) + ); + Assert.ok(msgData.includes("Subject: Test fccReplyToGroup")); + + // Test nsIMsgCompFields.ReplyToList. + gServer.resetTest(); + fields.subject = "Test fccReplyToList"; + await sendReply( + identity, + fields, + originalMsgURI, + Ci.nsIMsgCompType.ReplyToList + ); + await TestUtils.waitForCondition(() => gServer._daemon.post); + msgData = mailTestUtils.loadMessageToString( + localAccountUtils.inboxFolder, + mailTestUtils.getMsgHdrN(localAccountUtils.inboxFolder, 3) + ); + Assert.ok(msgData.includes("Subject: Test fccReplyToList")); +}); diff --git a/comm/mailnews/compose/test/unit/test_longLines.js b/comm/mailnews/compose/test/unit/test_longLines.js new file mode 100644 index 0000000000..cd75e75d38 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_longLines.js @@ -0,0 +1,232 @@ +/* + * Test ensuring that messages with "long lines" are transmitted correctly. + * Most of this test was copied from test_messageHeaders.js. + */ + +const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm"); + +var CompFields = CC( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields +); + +// Copied from jsmime.js. +function stringToTypedArray(buffer) { + var typedarray = new Uint8Array(buffer.length); + for (var i = 0; i < buffer.length; i++) { + typedarray[i] = buffer.charCodeAt(i); + } + return typedarray; +} + +function checkDraftHeadersAndBody( + expectedHeaders, + expectedBody, + charset = "UTF-8" +) { + let msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + checkMessageHeaders(msgData, expectedHeaders); + + // Get the message body, decode from base64 and check. + let endOfHeaders = msgData.indexOf("\r\n\r\n"); + let body = msgData.slice(endOfHeaders + 4); + let endOfBody = body.indexOf("\r\n\r\n"); + + if (endOfBody > 0) { + body = body.slice(0, endOfBody); + } else { + body = body.slice(0, body.length); + } + + // Remove line breaks and decode from base64 if required. + if (expectedHeaders["Content-Transfer-Encoding"] == "base64") { + body = atob(body.replace(/\r\n/g, "")); + } + + if (charset == "UTF-8") { + let expectedBinary = String.fromCharCode.apply( + undefined, + new TextEncoder("UTF-8").encode(expectedBody) + ); + Assert.equal(body, expectedBinary); + } else { + let strView = stringToTypedArray(body); + let decodedBody = new TextDecoder(charset).decode(strView); + Assert.equal(decodedBody, expectedBody); + } +} + +function checkMessageHeaders(msgData, expectedHeaders, partNum = "") { + let seen = false; + let handler = { + startPart(part, headers) { + if (part != partNum) { + return; + } + seen = true; + for (let header in expectedHeaders) { + let expected = expectedHeaders[header]; + if (expected === undefined) { + Assert.ok(!headers.has(header)); + } else { + let value = headers.getRawHeader(header); + Assert.equal(value.length, 1); + value[0] = value[0].replace(/boundary=[^;]*(;|$)/, "boundary=."); + Assert.equal(value[0], expected); + } + } + }, + }; + MimeParser.parseSync(msgData, handler, { + onerror(e) { + throw e; + }, + }); + Assert.ok(seen); +} + +// Create a line with 600 letters 'a' with acute accent, encoded as +// two bytes c3a1 in UTF-8. +let longMultibyteLine = "\u00E1".repeat(600); + +// And here a line with a Korean character, encoded as three bytes +// ec9588 in UTF-8. +let longMultibyteLineCJK = "안".repeat(400); + +// And some Japanese. +let longMultibyteLineJapanese = "語".repeat(450); + +async function testBodyWithLongLine() { + // Lines in the message body are split by CRLF according to RFC 5322, should + // be independent of the system. + let newline = "\r\n"; + + let fields = new CompFields(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + identity.fullName = "Me"; + identity.organization = "World Destruction Committee"; + fields.from = "Nobody <nobody@tinderbox.invalid>"; + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Message with 1200 byte line in body"; + let htmlMessage = + "<html><head>" + + '<meta http-equiv="content-type" content="text/html; charset=utf-8">' + + "</head><body>" + + longMultibyteLine + + "</body></html>\r\n\r\n"; + fields.body = htmlMessage; + await richCreateMessage(fields, [], identity); + checkDraftHeadersAndBody( + { + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "base64", + }, + htmlMessage + ); + + // Again, but this time as plain text. + fields.body = htmlMessage; + fields.forcePlainText = true; + fields.useMultipartAlternative = false; + await richCreateMessage(fields, [], identity); + checkDraftHeadersAndBody( + { + "Content-Type": "text/plain; charset=UTF-8; format=flowed", + "Content-Transfer-Encoding": "base64", + }, + longMultibyteLine + " " + newline + newline // Expected body: The message without the tags. + ); + + // Now CJK. + fields.forcePlainText = false; + htmlMessage = + "<html><head>" + + '<meta http-equiv="content-type" content="text/html; charset=utf-8">' + + "</head><body>" + + longMultibyteLineCJK + + "</body></html>\r\n\r\n"; + fields.body = htmlMessage; + await richCreateMessage(fields, [], identity); + checkDraftHeadersAndBody( + { + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "base64", + }, + htmlMessage + ); + + // Again, but this time as plain text. + fields.body = htmlMessage; + fields.forcePlainText = true; + fields.useMultipartAlternative = false; + await richCreateMessage(fields, [], identity); + checkDraftHeadersAndBody( + { + "Content-Type": "text/plain; charset=UTF-8; format=flowed", + "Content-Transfer-Encoding": "base64", + }, + longMultibyteLineCJK + " " + newline + newline // Expected body: The message without the tags. + ); + + // Now a test for ISO-2022-JP. + fields.forcePlainText = false; + htmlMessage = + "<html><head>" + + '<meta http-equiv="content-type" content="text/html; charset=ISO-2022-JP">' + + "</head><body>" + + longMultibyteLineJapanese + + "</body></html>\r\n\r\n"; + fields.body = htmlMessage; + await richCreateMessage(fields, [], identity); + checkDraftHeadersAndBody( + { + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "base64", + }, + htmlMessage + ); + + // Again, but this time as plain text. + fields.body = htmlMessage; + fields.forcePlainText = true; + fields.useMultipartAlternative = false; + await richCreateMessage(fields, [], identity); + + let expectedBody = longMultibyteLineJapanese + " " + newline + newline; + + checkDraftHeadersAndBody( + { + "Content-Type": "text/plain; charset=UTF-8; format=flowed", + "Content-Transfer-Encoding": "base64", + }, + expectedBody + ); + + // Again, but this time not flowed. + fields.body = htmlMessage; + Services.prefs.setBoolPref("mailnews.send_plaintext_flowed", false); + + await richCreateMessage(fields, [], identity); + checkDraftHeadersAndBody( + { + "Content-Type": "text/plain; charset=UTF-8", + "Content-Transfer-Encoding": "base64", + }, + expectedBody.replace(/ /g, "") // No spaces expected this time. + ); +} + +var tests = [testBodyWithLongLine]; + +function run_test() { + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + tests.forEach(x => add_task(x)); + run_next_test(); +} diff --git a/comm/mailnews/compose/test/unit/test_mailTelemetry.js b/comm/mailnews/compose/test/unit/test_mailTelemetry.js new file mode 100644 index 0000000000..28959d508f --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_mailTelemetry.js @@ -0,0 +1,79 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test telemetry related to mails sent. + */ + +let { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +let server; + +let kIdentityMail = "identity@foo.invalid"; +let kSender = "from@foo.invalid"; +let kTo = "to@foo.invalid"; + +const NUM_MAILS = 3; + +let deliveryListener = { + count: 0, + OnStartRunningUrl() {}, + OnStopRunningUrl() { + if (++this.count == NUM_MAILS) { + let scalars = TelemetryTestUtils.getProcessScalars("parent"); + Assert.equal( + scalars["tb.mails.sent"], + NUM_MAILS, + "Count of mails sent must be correct." + ); + } + }, +}; + +/** + * Check that we're counting mails sent. + */ +add_task(async function test_mails_sent() { + Services.telemetry.clearScalars(); + + server = setupServerDaemon(); + registerCleanupFunction(() => { + server.stop(); + }); + + // Test file + let testFile = do_get_file("data/message1.eml"); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // Start the fake SMTP server + server.start(); + let smtpServer = getBasicSmtpServer(server.port); + let identity = getSmtpIdentity(kIdentityMail, smtpServer); + + for (let i = 0; i < NUM_MAILS; i++) { + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + deliveryListener, + null, + null, + false, + "", + {}, + {} + ); + } + } catch (e) { + do_throw(e); + } +}); diff --git a/comm/mailnews/compose/test/unit/test_mailtoURL.js b/comm/mailnews/compose/test/unit/test_mailtoURL.js new file mode 100644 index 0000000000..a793c99974 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_mailtoURL.js @@ -0,0 +1,810 @@ +/* + * Test suite for mailto: URLs + */ + +var COMPOSE_HTML = Ci.nsIMsgCompFormat.HTML; +var COMPOSE_DEFAULT = Ci.nsIMsgCompFormat.Default; + +function run_test() { + function test(aTest) { + var uri = Services.io.newURI(aTest.url); + uri = uri.QueryInterface(Ci.nsIMailtoUrl); + + var to = {}, + cc = {}, + bcc = {}, + subject = {}, + body = {}, + html = {}, + reference = {}, + newsgroup = {}, + composeformat = {}; + uri.getMessageContents( + to, + cc, + bcc, + subject, + body, + html, + reference, + newsgroup, + composeformat + ); + Assert.equal(aTest.to, to.value); + Assert.equal(aTest.cc, cc.value); + Assert.equal(aTest.bcc, bcc.value); + Assert.equal(aTest.subject, subject.value); + Assert.equal(aTest.body, body.value); + Assert.equal(aTest.html, html.value); + Assert.equal(aTest.reference, reference.value); + Assert.equal(aTest.newsgroup, newsgroup.value); + Assert.equal(aTest.composeformat, composeformat.value); + Assert.equal(aTest.from, uri.fromPart); + Assert.equal(aTest.followupto, uri.followUpToPart); + Assert.equal(aTest.organization, uri.organizationPart); + Assert.equal(aTest.replyto, uri.replyToPart); + Assert.equal(aTest.priority, uri.priorityPart); + Assert.equal(aTest.newshost, uri.newsHostPart); + Assert.ok(uri.equals(uri)); + } + + for (var i = 0; i < tests.length; i++) { + test(tests[i]); + } + + // Test cloning reparses the url by checking the to field. + let uri = Services.io.newURI(tests[0].url).QueryInterface(Ci.nsIMailtoUrl); + var to = {}, + cc = {}, + bcc = {}, + subject = {}, + body = {}, + html = {}, + reference = {}, + newsgroup = {}, + composeformat = {}; + uri.getMessageContents( + to, + cc, + bcc, + subject, + body, + html, + reference, + newsgroup, + composeformat + ); + Assert.equal(to.value, tests[0].to); +} + +var tests = [ + { + url: "mailto:one@example.com", + to: "one@example.com", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:two@example.com?", + to: "two@example.com", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + /* the heirarchical-part address shouldn't be mime-decoded */ + { + url: "mailto:%3D%3FUTF-8%3FQ%3Fthree%3F%3D@example.com", + to: "=?UTF-8?Q?three?=@example.com", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + /* a to=address should be mime-decoded */ + { + url: "mailto:?to=%3D%3FUTF-8%3FQ%3Ffour%3F%3D@example.com", + to: "four@example.com", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:fivea@example.com?to=%3D%3FUTF-8%3FQ%3Ffiveb%3F%3D@example.com", + to: "fivea@example.com, fiveb@example.com", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:sixa@example.com?to=sixb@example.com&to=sixc@example.com", + to: "sixa@example.com, sixb@example.com, sixc@example.com", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?cc=seven@example.com", + to: "", + cc: "seven@example.com", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?cc=%3D%3FUTF-8%3FQ%3Feight%3F%3D@example.com", + to: "", + cc: "eight@example.com", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?bcc=nine@example.com", + to: "", + cc: "", + bcc: "nine@example.com", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?bcc=%3D%3FUTF-8%3FQ%3Ften%3F%3D@example.com", + to: "", + cc: "", + bcc: "ten@example.com", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?subject=foo", + to: "", + cc: "", + bcc: "", + subject: "foo", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?subject=%62%61%72", + to: "", + cc: "", + bcc: "", + subject: "bar", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?subject=%3D%3Futf-8%3FQ%3F%3DC2%3DA1encoded_subject%21%3F%3D", + to: "", + cc: "", + bcc: "", + subject: "¡encoded subject!", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?body=one%20body", + to: "", + cc: "", + bcc: "", + subject: "", + body: "one body", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?body=two%20bodies&body=two%20lines", + to: "", + cc: "", + bcc: "", + subject: "", + body: "two bodies\ntwo lines", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?html-part=html%20part", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "html part", + reference: "", + newsgroup: "", + composeformat: COMPOSE_HTML, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?html-body=html%20body", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "html body", + reference: "", + newsgroup: "", + composeformat: COMPOSE_HTML, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?html-part=html%20part&html-body=html-body%20trumps%20earlier%20html-part", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "html-body trumps earlier html-part", + reference: "", + newsgroup: "", + composeformat: COMPOSE_HTML, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?references=%3Cref1%40example.com%3E", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "<ref1@example.com>", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?in-reply-to=%3Crepl1%40example.com%3E", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "<repl1@example.com>", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: + "mailto:?references=%3Cref2%40example.com%3E" + + "&in-reply-to=%3Crepl2%40example.com%3E", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "<ref2@example.com> <repl2@example.com>", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: + "mailto:?references=%3Cref3%40example.com%3E%20%3Crepl3%40example.com%3E" + + "&in-reply-to=%3Crepl3%40example.com%3E", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "<ref3@example.com> <repl3@example.com>", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?newsgroups=mozilla.dev.apps.thunderbird", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "mozilla.dev.apps.thunderbird", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?newsgroups=%3D%3FUTF-8%3FQ%3Fmozilla.test.multimedia%3F%3D", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "mozilla.test.multimedia", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?from=notlikely@example.com", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "notlikely@example.com", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?from=%3D%3FUTF-8%3FQ%3Fme@example.com%3F%3D", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "me@example.com", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?followup-to=mozilla.dev.planning", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "mozilla.dev.planning", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?followup-to=%3D%3FUTF-8%3FQ%3Fmozilla.test%3F%3D", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "mozilla.test", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?organization=very%20little", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "very little", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?organization=%3D%3FUTF-8%3FQ%3Fmicroscopic%3F%3D", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "microscopic", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?reply-to=notme@example.com", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "notme@example.com", + priority: "", + newshost: "", + }, + { + url: "mailto:?reply-to=%3D%3FUTF-8%3FB%3Fw4VrZQ%3D%3D%3F%3D%20%3Cake@example.org%3E", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "Åke <ake@example.org>", + priority: "", + newshost: "", + }, + { + url: "mailto:?priority=1%20(People%20Are%20Dying!!1!)", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "1 (People Are Dying!!1!)", + newshost: "", + }, + { + url: "mailto:?priority=%3D%3FUTF-8%3FQ%3F4%3F%3D", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "4", + newshost: "", + }, + { + url: "mailto:?newshost=news.mozilla.org", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "news.mozilla.org", + }, + { + url: "mailto:?newshost=%3D%3FUTF-8%3FQ%3Fnews.example.org%3F%3D", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "news.example.org", + }, + { + url: "mailto:?%74%4F=to&%73%55%62%4A%65%43%74=subject&%62%4F%64%59=body&%63%43=cc&%62%43%63=bcc", + to: "to", + cc: "cc", + bcc: "bcc", + subject: "subject", + body: "body", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:to1?%74%4F=to2&to=to3&subject=&%73%55%62%4A%65%43%74=subject&%62%4F%64%59=line1&body=line2&%63%43=cc1&cc=cc2&%62%43%63=bcc1&bcc=bcc2", + to: "to1, to2, to3", + cc: "cc1, cc2", + bcc: "bcc1, bcc2", + subject: "subject", + body: "line1\nline2", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: "mailto:?nto=1&nsubject=2&nbody=3&ncc=4&nbcc=5", + to: "", + cc: "", + bcc: "", + subject: "", + body: "", + html: "", + reference: "", + newsgroup: "", + composeformat: COMPOSE_DEFAULT, + from: "", + followupto: "", + organization: "", + replyto: "", + priority: "", + newshost: "", + }, + { + url: + "mailto:%CE%B1?cc=%CE%B2&bcc=%CE%B3&subject=%CE%B4&body=%CE%B5" + + "&html-body=%CE%BE&newsgroups=%CE%B6&from=%CE%B7&followup-to=%CE%B8" + + "&organization=%CE%B9&reply-to=%CE%BA&priority=%CE%BB&newshost=%CE%BC", + to: "α", + cc: "β", + bcc: "γ", + subject: "δ", + body: "ε", + html: "ξ", + reference: "", // we expect this field to be ASCII-only + newsgroup: "ζ", + composeformat: COMPOSE_HTML, + from: "η", + followupto: "θ", + organization: "ι", + replyto: "κ", + priority: "λ", + newshost: "μ", + }, +]; diff --git a/comm/mailnews/compose/test/unit/test_messageBody.js b/comm/mailnews/compose/test/unit/test_messageBody.js new file mode 100644 index 0000000000..14c44b59f8 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_messageBody.js @@ -0,0 +1,206 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test suite for message body. + */ + +localAccountUtils.loadLocalMailAccount(); + +/** + * Test trailing whitespace is QP encoded. + */ +add_task(async function testQP() { + // Together with fields.forceMsgEncoding, force quote-printable encoding. + Services.prefs.setBoolPref("mail.strictly_mime", true); + + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + let CompFields = CC( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields + ); + + // Test QP works for ascii text. + + let fields = new CompFields(); + fields.forceMsgEncoding = true; + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Test QP encoding for trailing whitespace"; + fields.body = "A line with trailing whitespace\t "; + await richCreateMessage(fields, [], identity); + + let msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + Assert.ok( + msgData.includes("A line with trailing whitespace\t=20"), + "QP for ascii should work" + ); + + // Test QP works for non-ascii text. + + fields = new CompFields(); + fields.forceMsgEncoding = true; + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Test QP encoding for non-ascii and trailing tab"; + fields.body = "記: base64 is used if unprintable > 10% \t"; + await richCreateMessage(fields, [], identity); + + msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + Assert.ok( + msgData.includes("=E8=A8=98: base64 is used if unprintable > 10% =09"), + "QP for non-ascii should work" + ); + + // Test leading space is preserved. + + fields = new CompFields(); + fields.forceMsgEncoding = true; + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Leading space is valid in a quoted printable message"; + fields.body = "123456789" + " 123456789".repeat(6) + "1234 56789"; + await richCreateMessage(fields, [], identity); + + msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + let endOfHeaders = msgData.indexOf("\r\n\r\n"); + let body = msgData.slice(endOfHeaders + 4); + + Assert.equal( + body.trimRight("\r\n"), + "123456789 123456789 123456789 123456789 123456789 123456789 1234567891234=\r\n 56789" + ); + + Services.prefs.clearUserPref("mail.strictly_mime"); +}); + +/** + * Test QP is not used together with format=flowed. + */ +add_task(async function testNoQPWithFormatFlowed() { + // Together with fields.forceMsgEncoding, force quote-printable encoding. + Services.prefs.setBoolPref("mail.strictly_mime", true); + + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + fields.forceMsgEncoding = true; + fields.forcePlainText = true; + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Test QP encoding for trailing whitespace"; + fields.body = "A line with trailing whitespace\t "; + await richCreateMessage(fields, [], identity); + + let msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + Assert.ok( + msgData.includes( + "Content-Type: text/plain; charset=UTF-8; format=flowed\r\nContent-Transfer-Encoding: base64" + ), + "format=flowed should be used" + ); + Assert.ok( + !msgData.includes("quoted-printable"), + "quoted-printable should not be used" + ); + + Services.prefs.clearUserPref("mail.strictly_mime"); +}); + +/** + * Test plain text body is wrapped correctly with different mailnews.wraplength + * pref value. + */ +add_task(async function testWrapLength() { + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + let CompFields = CC( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields + ); + + let word = "abcd "; + let body = word.repeat(20); + + let fields = new CompFields(); + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Test text wrapping"; + fields.body = `<html><body>${body}</body></html>`; + fields.forcePlainText = true; + await richCreateMessage(fields, [], identity); + + let msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + Assert.equal( + getMessageBody(msgData), + // Default wrap length is 72. + word.repeat(14) + "\r\n" + word.repeat(6).trim(), + "Text wraps at 72 by default" + ); + + // 0 means no wrap. + Services.prefs.setIntPref("mailnews.wraplength", 0); + + await richCreateMessage(fields, [], identity); + + msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + Assert.equal( + getMessageBody(msgData), + body.trim(), + "Should not wrap when wraplength is 0" + ); + + Services.prefs.clearUserPref("mailnews.wraplength"); +}); + +/** + * Test handling of trailing NBSP. + */ +add_task(async function testNBSP() { + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.subject = "Test text wrapping"; + // The character after `test` is NBSP. + fields.body = "<html><body>åäö test <br></body></html>"; + fields.forcePlainText = true; + await richCreateMessage(fields, [], identity); + + let msgData = mailTestUtils.loadMessageToUTF16String( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + Assert.equal( + getMessageBody(msgData), + "åäö test", + "Trailing NBSP should be removed" + ); +}); diff --git a/comm/mailnews/compose/test/unit/test_messageHeaders.js b/comm/mailnews/compose/test/unit/test_messageHeaders.js new file mode 100644 index 0000000000..58765e219f --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_messageHeaders.js @@ -0,0 +1,812 @@ +/* + * Test suite for ensuring that the headers of messages are set properly. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm"); + +var CompFields = CC( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields +); + +function makeAttachment(opts = {}) { + let attachment = Cc[ + "@mozilla.org/messengercompose/attachment;1" + ].createInstance(Ci.nsIMsgAttachment); + for (let key in opts) { + attachment[key] = opts[key]; + } + return attachment; +} + +function sendMessage(fieldParams, identity, opts = {}, attachments = []) { + // Initialize compose fields + let fields = new CompFields(); + for (let key in fieldParams) { + fields[key] = fieldParams[key]; + } + for (let attachment of attachments) { + fields.addAttachment(attachment); + } + + // Initialize compose params + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + params.composeFields = fields; + for (let key in opts) { + params[key] = opts[key]; + } + + // Send the message + let msgCompose = MailServices.compose.initCompose(params); + let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance( + Ci.nsIMsgProgress + ); + let promise = new Promise((resolve, reject) => { + progressListener.resolve = resolve; + progressListener.reject = reject; + }); + progress.registerListener(progressListener); + msgCompose.sendMsg( + Ci.nsIMsgSend.nsMsgDeliverNow, + identity, + "", + null, + progress + ); + return promise; +} + +function checkDraftHeaders(expectedHeaders, partNum = "") { + let msgData = mailTestUtils.loadMessageToString( + gDraftFolder, + mailTestUtils.firstMsgHdr(gDraftFolder) + ); + checkMessageHeaders(msgData, expectedHeaders, partNum); +} + +function checkMessageHeaders(msgData, expectedHeaders, partNum = "") { + let seen = false; + let handler = { + startPart(part, headers) { + if (part != partNum) { + return; + } + seen = true; + for (let header in expectedHeaders) { + let expected = expectedHeaders[header]; + if (expected === undefined) { + Assert.ok( + !headers.has(header), + `Should not have header named "${header}"` + ); + } else { + let value = headers.getRawHeader(header); + Assert.equal( + value && value.length, + 1, + `Should have exactly one header named "${header}"` + ); + value[0] = value[0].replace(/boundary=[^;]*(;|$)/, "boundary=."); + Assert.equal(value[0], expected); + } + } + }, + }; + MimeParser.parseSync(msgData, handler, { + onerror(e) { + throw e; + }, + }); + Assert.ok(seen); +} + +async function testEnvelope() { + let fields = new CompFields(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + identity.fullName = "Me"; + identity.organization = "World Destruction Committee"; + fields.from = "Nobody <nobody@tinderbox.invalid>"; + fields.to = "Nobody <nobody@tinderbox.invalid>"; + fields.cc = "Alex <alex@tinderbox.invalid>"; + fields.bcc = "Boris <boris@tinderbox.invalid>"; + fields.replyTo = "Charles <charles@tinderbox.invalid>"; + fields.organization = "World Salvation Committee"; + fields.subject = "This is an obscure reference"; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + // As of bug 87987, the identity does not override the from header. + From: "Nobody <nobody@tinderbox.invalid>", + // The identity should override the organization field here. + Organization: "World Destruction Committee", + To: "Nobody <nobody@tinderbox.invalid>", + Cc: "Alex <alex@tinderbox.invalid>", + Bcc: "Boris <boris@tinderbox.invalid>", + "Reply-To": "Charles <charles@tinderbox.invalid>", + Subject: "This is an obscure reference", + }); +} + +async function testI18NEnvelope() { + let fields = new CompFields(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + identity.fullName = "ケツァルコアトル"; + identity.organization = "Comité de la destruction du monde"; + fields.to = "Émile <nobody@tinderbox.invalid>"; + fields.cc = "André Chopin <alex@tinderbox.invalid>"; + fields.bcc = "Étienne <boris@tinderbox.invalid>"; + fields.replyTo = "Frédéric <charles@tinderbox.invalid>"; + fields.subject = "Ceci n'est pas un référence obscure"; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + From: "=?UTF-8?B?44Kx44OE44Kh44Or44Kz44Ki44OI44Or?= <from@tinderbox.invalid>", + Organization: "=?UTF-8?Q?Comit=C3=A9_de_la_destruction_du_monde?=", + To: "=?UTF-8?B?w4ltaWxl?= <nobody@tinderbox.invalid>", + Cc: "=?UTF-8?Q?Andr=C3=A9_Chopin?= <alex@tinderbox.invalid>", + Bcc: "=?UTF-8?Q?=C3=89tienne?= <boris@tinderbox.invalid>", + "Reply-To": "=?UTF-8?B?RnLDqWTDqXJpYw==?= <charles@tinderbox.invalid>", + Subject: "=?UTF-8?Q?Ceci_n=27est_pas_un_r=C3=A9f=C3=A9rence_obscure?=", + }); +} + +async function testIDNEnvelope() { + let fields = new CompFields(); + let domain = "ケツァルコアトル.invalid"; + // We match against rawHeaderText, so we need to encode the string as a binary + // string instead of a unicode string. + let utf8Domain = String.fromCharCode.apply( + undefined, + new TextEncoder("UTF-8").encode(domain) + ); + // Bug 1034658: nsIMsgIdentity doesn't like IDN in its email addresses. + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + fields.to = "Nobody <nobody@" + domain + ">"; + fields.cc = "Alex <alex@" + domain + ">"; + fields.bcc = "Boris <boris@" + domain + ">"; + fields.replyTo = "Charles <charles@" + domain + ">"; + fields.subject = "This is an obscure reference"; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + // The identity sets the from field here. + From: "from@tinderbox.invalid", + To: "Nobody <nobody@" + utf8Domain + ">", + Cc: "Alex <alex@" + utf8Domain + ">", + Bcc: "Boris <boris@" + utf8Domain + ">", + "Reply-To": "Charles <charles@" + utf8Domain + ">", + Subject: "This is an obscure reference", + }); +} + +async function testDraftInfo() { + let fields = new CompFields(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + FCC: identity.fccFolder, + "X-Identity-Key": identity.key, + "X-Mozilla-Draft-Info": + "internal/draft; " + + "vcard=0; receipt=0; DSN=0; uuencode=0; attachmentreminder=0; deliveryformat=4", + }); + + fields.attachVCard = true; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "X-Mozilla-Draft-Info": + "internal/draft; " + + "vcard=1; receipt=0; DSN=0; uuencode=0; attachmentreminder=0; deliveryformat=4", + }); + + fields.returnReceipt = true; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "X-Mozilla-Draft-Info": + "internal/draft; " + + "vcard=1; receipt=1; DSN=0; uuencode=0; attachmentreminder=0; deliveryformat=4", + }); + + fields.DSN = true; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "X-Mozilla-Draft-Info": + "internal/draft; " + + "vcard=1; receipt=1; DSN=1; uuencode=0; attachmentreminder=0; deliveryformat=4", + }); + + fields.attachmentReminder = true; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "X-Mozilla-Draft-Info": + "internal/draft; " + + "vcard=1; receipt=1; DSN=1; uuencode=0; attachmentreminder=1; deliveryformat=4", + }); + + fields.deliveryFormat = Ci.nsIMsgCompSendFormat.Both; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "X-Mozilla-Draft-Info": + "internal/draft; " + + "vcard=1; receipt=1; DSN=1; uuencode=0; attachmentreminder=1; deliveryformat=3", + }); +} + +async function testOtherHeadersAgentParam(sendAgent, minimalAgent) { + Services.prefs.setBoolPref("mailnews.headers.sendUserAgent", sendAgent); + if (sendAgent) { + Services.prefs.setBoolPref( + "mailnews.headers.useMinimalUserAgent", + minimalAgent + ); + } + + let fields = new CompFields(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + fields.priority = "high"; + fields.references = "<fake@tinderbox.invalid> <more@test.invalid>"; + fields.setHeader("X-Fake-Header", "124"); + let before = Date.now(); + let msgHdr = await richCreateMessage(fields, [], identity); + let after = Date.now(); + let msgData = mailTestUtils.loadMessageToString(msgHdr.folder, msgHdr); + let expectedAgent = undefined; // !sendAgent + if (sendAgent) { + if (minimalAgent) { + expectedAgent = Services.strings + .createBundle("chrome://branding/locale/brand.properties") + .GetStringFromName("brandFullName"); + } else { + expectedAgent = Cc[ + "@mozilla.org/network/protocol;1?name=http" + ].getService(Ci.nsIHttpProtocolHandler).userAgent; + } + } + checkMessageHeaders(msgData, { + "Mime-Version": "1.0", + "User-Agent": expectedAgent, + "X-Priority": "2 (High)", + References: "<fake@tinderbox.invalid> <more@test.invalid>", + "In-Reply-To": "<more@test.invalid>", + "X-Fake-Header": "124", + }); + + // Check headers with dynamic content + let headers = MimeParser.extractHeaders(msgData); + Assert.ok(headers.has("Message-Id")); + Assert.ok( + headers.getRawHeader("Message-Id")[0].endsWith("@tinderbox.invalid>") + ); + // This is a very special crafted check. We don't know when the message was + // actually created, but we have bounds on it, from above. From + // experimentation, there are a few ways you can create dates that Date.parse + // can't handle (specifically related to how 2-digit years). However, the + // optimal RFC 5322 form is supported by Date.parse. If Date.parse fails, we + // have a form that we shouldn't be using anyways. + let date = new Date(headers.getRawHeader("Date")[0]); + // If we have clock skew within the test, then our results are going to be + // meaningless. Hopefully, this is only rarely the case. + if (before > after) { + info("Clock skew detected, skipping date check"); + } else { + // In case this all took place within one second, remove sub-millisecond + // timing (Date headers only carry second-level precision). + before = before - (before % 1000); + after = after - (after % 1000); + info(before + " <= " + date + " <= " + after + "?"); + Assert.ok(before <= date && date <= after); + } + + // We truncate too-long References. Check this. + let references = []; + for (let i = 0; i < 100; i++) { + references.push("<" + i + "@test.invalid>"); + } + let expected = references.slice(47); + expected.unshift(references[0]); + fields.references = references.join(" "); + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + References: expected.join(" "), + "In-Reply-To": references[references.length - 1], + }); +} + +/** + * Tests that the domain for the Message-Id header defaults to the domain of the + * identity's address. + */ +async function testMessageIdUseIdentityAddress() { + const expectedMessageIdHostname = "tinderbox.test"; + + const identity = getSmtpIdentity( + `from@${expectedMessageIdHostname}`, + getBasicSmtpServer() + ); + + await createMsgAndCompareMessageId(identity, null, expectedMessageIdHostname); +} + +/** + * Tests that if a custom address (with a custom domain) is used when composing a + * message, the domain in this address takes precendence over the domain of the + * identity's address to generate the value for the Message-Id header. + */ +async function testMessageIdUseFromDomain() { + const expectedMessageIdHostname = "another-tinderbox.test"; + + const identity = getSmtpIdentity("from@tinderbox.test", getBasicSmtpServer()); + + // Set the From header to an address that uses a different domain than + // the identity. + const fields = new CompFields(); + fields.from = `Nobody <nobody@${expectedMessageIdHostname}>`; + + await createMsgAndCompareMessageId( + identity, + fields, + expectedMessageIdHostname + ); +} + +/** + * Tests that if the identity has a "FQDN" attribute, it takes precedence to use as the + * domain for the Message-Id header over any other domain or address. + */ +async function testMessageIdUseIdentityAttribute() { + const expectedMessageIdHostname = "my-custom-fqdn.test"; + + const identity = getSmtpIdentity("from@tinderbox.test", getBasicSmtpServer()); + identity.setCharAttribute("FQDN", expectedMessageIdHostname); + + // Set the From header to an address that uses a different domain than + // the identity. + const fields = new CompFields(); + fields.from = "Nobody <nobody@another-tinderbox.test>"; + + await createMsgAndCompareMessageId( + identity, + fields, + expectedMessageIdHostname + ); +} + +/** + * Util function to create a message using the given identity and fields, + * and test that the message ID that was generated for it has the correct + * host name. + * + * @param {nsIMsgIdentity} identity - The identity to use to create the message. + * @param {?nsIMsgCompFields} fields - The compose fields to use. If not provided, + * default fields are used. + * @param {string} expectedMessageIdHostname - The expected host name of the + * Message-Id header. + */ +async function createMsgAndCompareMessageId( + identity, + fields, + expectedMessageIdHostname +) { + if (!fields) { + fields = new CompFields(); + } + + let msgHdr = await richCreateMessage(fields, [], identity); + let msgData = mailTestUtils.loadMessageToString(msgHdr.folder, msgHdr); + let headers = MimeParser.extractHeaders(msgData); + + // As of bug 1727181, the identity does not override the message-id header. + Assert.ok(headers.has("Message-Id"), "the message has a Message-Id header"); + Assert.ok( + headers + .getRawHeader("Message-Id")[0] + .endsWith(`@${expectedMessageIdHostname}>`), + `the hostname for the Message-Id header should be ${expectedMessageIdHostname}` + ); +} + +async function testOtherHeadersFullAgent() { + await testOtherHeadersAgentParam(true, false); +} + +async function testOtherHeadersMinimalAgent() { + await testOtherHeadersAgentParam(true, true); +} + +async function testOtherHeadersNoAgent() { + await testOtherHeadersAgentParam(false, undefined); +} + +async function testNewsgroups() { + let fields = new CompFields(); + let nntpServer = localAccountUtils.create_incoming_server( + "nntp", + 534, + "", + "" + ); + nntpServer + .QueryInterface(Ci.nsINntpIncomingServer) + .subscribeToNewsgroup("mozilla.test"); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + fields.newsgroups = "mozilla.test, mozilla.test.multimedia"; + fields.followupTo = "mozilla.test"; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + // The identity should override the compose fields here. + Newsgroups: "mozilla.test,mozilla.test.multimedia", + "Followup-To": "mozilla.test", + "X-Mozilla-News-Host": "localhost", + }); +} + +async function testSendHeaders() { + let fields = new CompFields(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + identity.setCharAttribute("headers", "bah,humbug"); + identity.setCharAttribute( + "header.bah", + "X-Custom-1: A header value: with a colon" + ); + identity.setUnicharAttribute("header.humbug", "X-Custom-2: Enchanté"); + identity.setCharAttribute("subscribed_mailing_lists", "list@test.invalid"); + identity.setCharAttribute( + "replyto_mangling_mailing_lists", + "replyto@test.invalid" + ); + fields.to = "list@test.invalid"; + fields.cc = "not-list@test.invalid"; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "X-Custom-1": "A header value: with a colon", + "X-Custom-2": "=?UTF-8?B?RW5jaGFudMOp?=", + "Mail-Followup-To": "list@test.invalid, not-list@test.invalid", + "Mail-Reply-To": undefined, + }); + + // Don't set the M-F-T header if there's no list. + fields.to = "replyto@test.invalid"; + fields.cc = ""; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "X-Custom-1": "A header value: with a colon", + "X-Custom-2": "=?UTF-8?B?RW5jaGFudMOp?=", + "Mail-Reply-To": "from@tinderbox.invalid", + "Mail-Followup-To": undefined, + }); +} + +async function testContentHeaders() { + // Disable RFC 2047 fallback + Services.prefs.setIntPref("mail.strictly_mime.parm_folding", 2); + let fields = new CompFields(); + fields.body = "A body"; + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "7bit", + }); + + // non-ASCII body should be 8-bit... + fields.body = "Archæologist"; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "8bit", + }); + + // Attachments + fields.body = ""; + let plainAttachment = makeAttachment({ + url: "data:text/plain,oïl", + name: "attachment.txt", + }); + let plainAttachmentHeaders = { + "Content-Type": "text/plain; charset=UTF-8", + "Content-Transfer-Encoding": "base64", + "Content-Disposition": 'attachment; filename="attachment.txt"', + }; + await richCreateMessage(fields, [plainAttachment], identity); + checkDraftHeaders( + { + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "7bit", + }, + "1" + ); + checkDraftHeaders(plainAttachmentHeaders, "2"); + + plainAttachment.name = "oïl.txt"; + plainAttachmentHeaders["Content-Disposition"] = + "attachment; filename*=UTF-8''%6F%C3%AF%6C%2E%74%78%74"; + await richCreateMessage(fields, [plainAttachment], identity); + checkDraftHeaders(plainAttachmentHeaders, "2"); + + plainAttachment.name = "\ud83d\udca9.txt"; + plainAttachmentHeaders["Content-Disposition"] = + "attachment; filename*=UTF-8''%F0%9F%92%A9%2E%74%78%74"; + await richCreateMessage(fields, [plainAttachment], identity); + checkDraftHeaders(plainAttachmentHeaders, "2"); + + let httpAttachment = makeAttachment({ + url: "data:text/html,<html></html>", + name: "attachment.html", + }); + let httpAttachmentHeaders = { + "Content-Type": "text/html; charset=UTF-8", + "Content-Disposition": 'attachment; filename="attachment.html"', + "Content-Location": "data:text/html,<html></html>", + }; + await richCreateMessage(fields, [httpAttachment], identity); + checkDraftHeaders( + { + "Content-Location": undefined, + }, + "1" + ); + checkDraftHeaders(httpAttachmentHeaders, "2"); + + let cloudAttachment = makeAttachment({ + url: Services.io.newFileURI(do_get_file("data/test-UTF-8.txt")).spec, + sendViaCloud: true, + htmlAnnotation: + "<html><body>This is an html placeholder file.</body></html>", + cloudFileAccountKey: "akey", + cloudPartHeaderData: "0123456789ABCDE", + name: "attachment.html", + contentLocation: "http://localhost.invalid/", + }); + let cloudAttachmentHeaders = { + "Content-Type": "text/html; charset=utf-8", + "X-Mozilla-Cloud-Part": + "cloudFile; " + + "url=http://localhost.invalid/; " + + "provider=akey; " + + 'data="0123456789ABCDE"', + }; + await richCreateMessage(fields, [cloudAttachment], identity); + checkDraftHeaders(cloudAttachmentHeaders, "2"); + + // Cloud attachment with non-ascii file name. + cloudAttachment = makeAttachment({ + url: Services.io.newFileURI(do_get_file("data/test-UTF-8.txt")).spec, + sendViaCloud: true, + htmlAnnotation: + "<html><body>This is an html placeholder file.</body></html>", + cloudFileAccountKey: "akey", + cloudPartHeaderData: "0123456789ABCDE", + name: "ファイル.txt", + contentLocation: "http://localhost.invalid/", + }); + cloudAttachmentHeaders = { + "Content-Type": "text/html; charset=utf-8", + "X-Mozilla-Cloud-Part": + "cloudFile; " + + "url=http://localhost.invalid/; " + + "provider=akey; " + + 'data="0123456789ABCDE"', + }; + await richCreateMessage(fields, [cloudAttachment], identity); + checkDraftHeaders(cloudAttachmentHeaders, "2"); + + // Some multipart/alternative tests. + fields.body = "Some text"; + fields.forcePlainText = false; + fields.useMultipartAlternative = true; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "Content-Type": "multipart/alternative; boundary=.", + }); + checkDraftHeaders( + { + "Content-Type": "text/plain; charset=UTF-8; format=flowed", + "Content-Transfer-Encoding": "7bit", + }, + "1" + ); + checkDraftHeaders( + { + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "7bit", + }, + "2" + ); + + // multipart/mixed + // + multipart/alternative + // + text/plain + // + text/html + // + text/plain attachment + await richCreateMessage(fields, [plainAttachment], identity); + checkDraftHeaders({ + "Content-Type": "multipart/mixed; boundary=.", + }); + checkDraftHeaders( + { + "Content-Type": "multipart/alternative; boundary=.", + }, + "1" + ); + checkDraftHeaders( + { + "Content-Type": "text/plain; charset=UTF-8; format=flowed", + "Content-Transfer-Encoding": "7bit", + }, + "1.1" + ); + checkDraftHeaders( + { + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "7bit", + }, + "1.2" + ); + checkDraftHeaders(plainAttachmentHeaders, "2"); + + // Three attachments, and a multipart/alternative. Oh the humanity! + await richCreateMessage( + fields, + [plainAttachment, httpAttachment, cloudAttachment], + identity + ); + checkDraftHeaders({ + "Content-Type": "multipart/mixed; boundary=.", + }); + checkDraftHeaders( + { + "Content-Type": "multipart/alternative; boundary=.", + }, + "1" + ); + checkDraftHeaders( + { + "Content-Type": "text/plain; charset=UTF-8; format=flowed", + "Content-Transfer-Encoding": "7bit", + }, + "1.1" + ); + checkDraftHeaders( + { + "Content-Type": "text/html; charset=UTF-8", + "Content-Transfer-Encoding": "7bit", + }, + "1.2" + ); + checkDraftHeaders(plainAttachmentHeaders, "2"); + checkDraftHeaders(httpAttachmentHeaders, "3"); + checkDraftHeaders(cloudAttachmentHeaders, "4"); + + // Test a request for plain text with text/html. + fields.forcePlainText = true; + fields.useMultipartAlternative = false; + await richCreateMessage(fields, [], identity); + checkDraftHeaders({ + "Content-Type": "text/plain; charset=UTF-8; format=flowed", + "Content-Transfer-Encoding": "7bit", + }); +} + +async function testSentMessage() { + let server = setupServerDaemon(); + let daemon = server._daemon; + server.start(); + try { + let localserver = getBasicSmtpServer(server.port); + let identity = getSmtpIdentity("test@tinderbox.invalid", localserver); + await sendMessage( + { + to: "Nobody <nobody@tinderbox.invalid>", + cc: "Alex <alex@tinderbox.invalid>", + bcc: "Boris <boris@tinderbox.invalid>", + replyTo: "Charles <charles@tinderbox.invalid>", + }, + identity, + {}, + [] + ); + checkMessageHeaders(daemon.post, { + From: "test@tinderbox.invalid", + To: "Nobody <nobody@tinderbox.invalid>", + Cc: "Alex <alex@tinderbox.invalid>", + Bcc: undefined, + "Reply-To": "Charles <charles@tinderbox.invalid>", + "X-Mozilla-Status": undefined, + "X-Mozilla-Keys": undefined, + "X-Mozilla-Draft-Info": undefined, + Fcc: undefined, + }); + server.resetTest(); + await sendMessage({ bcc: "Somebody <test@tinderbox.invalid" }, identity); + checkMessageHeaders(daemon.post, { + To: "undisclosed-recipients: ;", + }); + server.resetTest(); + await sendMessage( + { + to: "Somebody <test@tinderbox.invalid>", + returnReceipt: true, + receiptHeaderType: Ci.nsIMsgMdnGenerator.eDntRrtType, + }, + identity + ); + checkMessageHeaders(daemon.post, { + "Disposition-Notification-To": "test@tinderbox.invalid", + "Return-Receipt-To": "test@tinderbox.invalid", + }); + server.resetTest(); + let cloudAttachment = makeAttachment({ + url: Services.io.newFileURI(do_get_file("data/test-UTF-8.txt")).spec, + sendViaCloud: true, + htmlAnnotation: + "<html><body>This is an html placeholder file.</body></html>", + cloudFileAccountKey: "akey", + cloudPartHeaderData: "0123456789ABCDE", + name: "attachment.html", + contentLocation: "http://localhost.invalid/", + }); + await sendMessage({ to: "test@tinderbox.invalid" }, identity, {}, [ + cloudAttachment, + ]); + checkMessageHeaders( + daemon.post, + { + "Content-Type": "text/html; charset=utf-8", + "X-Mozilla-Cloud-Part": "cloudFile; url=http://localhost.invalid/", + }, + "2" + ); + } finally { + server.stop(); + } +} + +var tests = [ + testEnvelope, + testI18NEnvelope, + testIDNEnvelope, + testDraftInfo, + testOtherHeadersFullAgent, + testOtherHeadersMinimalAgent, + testOtherHeadersNoAgent, + testNewsgroups, + testSendHeaders, + testContentHeaders, + testSentMessage, + testMessageIdUseIdentityAddress, + testMessageIdUseFromDomain, + testMessageIdUseIdentityAttribute, +]; + +function run_test() { + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + tests.forEach(x => add_task(x)); + run_next_test(); +} diff --git a/comm/mailnews/compose/test/unit/test_nsIMsgCompFields.js b/comm/mailnews/compose/test/unit/test_nsIMsgCompFields.js new file mode 100644 index 0000000000..69c1b753b6 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_nsIMsgCompFields.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test that nsIMsgCompFields works properly + +var nsMsgCompFields = Components.Constructor( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields +); + +function check_headers(enumerator, container) { + let checkValues = new Set(container.map(header => header.toLowerCase())); + for (let value of enumerator) { + value = value.toLowerCase(); + Assert.ok(checkValues.has(value)); + checkValues.delete(value); + } + Assert.equal(checkValues.size, 0); +} + +function run_test() { + let fields = new nsMsgCompFields(); + Assert.ok(fields instanceof Ci.nsIMsgCompFields); + Assert.ok(fields instanceof Ci.msgIStructuredHeaders); + Assert.ok(fields instanceof Ci.msgIWritableStructuredHeaders); + check_headers(fields.headerNames, []); + Assert.ok(!fields.hasRecipients); + + // Try some basic headers + fields.setHeader("From", [{ name: "", email: "a@test.invalid" }]); + let from = fields.getHeader("from"); + Assert.equal(from.length, 1); + Assert.equal(from[0].email, "a@test.invalid"); + check_headers(fields.headerNames, ["From"]); + Assert.ok(!fields.hasRecipients); + + // Add a To header + fields.setHeader("To", [{ name: "", email: "b@test.invalid" }]); + check_headers(fields.headerNames, ["From", "To"]); + Assert.ok(fields.hasRecipients); + + // Delete a header... + fields.deleteHeader("from"); + Assert.equal(fields.getHeader("From"), undefined); + check_headers(fields.headerNames, ["To"]); + + // Subject should work and not convert to RFC 2047. + fields.subject = "\u79c1\u306f\u4ef6\u540d\u5348\u524d"; + Assert.equal(fields.subject, "\u79c1\u306f\u4ef6\u540d\u5348\u524d"); + Assert.equal( + fields.getHeader("Subject"), + "\u79c1\u306f\u4ef6\u540d\u5348\u524d" + ); + + // Check header synchronization. + fields.from = "a@test.invalid"; + Assert.equal(fields.from, "a@test.invalid"); + Assert.equal(fields.getHeader("From")[0].email, "a@test.invalid"); + fields.from = null; + Assert.equal(fields.getHeader("From"), undefined); +} diff --git a/comm/mailnews/compose/test/unit/test_nsMsgCompose1.js b/comm/mailnews/compose/test/unit/test_nsMsgCompose1.js new file mode 100644 index 0000000000..700232b46e --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_nsMsgCompose1.js @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/** + * Tests nsMsgCompose expandMailingLists. + */ + +/** + * Helper to check population worked as expected. + * + * @param {string} aTo - Text in the To field. + * @param {string} aCheckTo - The expected To addresses (after possible list population) + */ +function checkPopulate(aTo, aCheckTo) { + var msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance( + Ci.nsIMsgCompose + ); + + // Set up some basic fields for compose. + var fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + fields.to = aTo; + + // Set up some params + var params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + + params.composeFields = fields; + + msgCompose.initialize(params); + + msgCompose.expandMailingLists(); + let addresses = fields.getHeader("To"); + let checkEmails = MailServices.headerParser.parseDecodedHeader(aCheckTo); + Assert.equal(addresses.length, checkEmails.length); + for (let i = 0; i < addresses.length; i++) { + Assert.equal(addresses[i].name, checkEmails[i].name); + Assert.equal(addresses[i].email, checkEmails[i].email); + } +} + +function run_test() { + loadABFile("../../../data/abLists1", kPABData.fileName); + loadABFile("../../../data/abLists2", kCABData.fileName); + + // Test - Check we can initialize with fewest specified + // parameters and don't fail/crash like we did in bug 411646. + + var msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance( + Ci.nsIMsgCompose + ); + + // Set up some params + var params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + + msgCompose.initialize(params); + + // Test - expandMailingLists basic functionality. + + // Re-initialize + msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance( + Ci.nsIMsgCompose + ); + + // Set up some basic fields for compose. + var fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + // These aren't in the address book copied above. + fields.from = "test1@foo1.invalid"; + fields.to = "test2@foo1.invalid"; + fields.cc = "test3@foo1.invalid"; + fields.bcc = "test4@foo1.invalid"; + + // Set up some params + params = Cc["@mozilla.org/messengercompose/composeparams;1"].createInstance( + Ci.nsIMsgComposeParams + ); + + params.composeFields = fields; + + msgCompose.initialize(params); + + msgCompose.expandMailingLists(); + Assert.equal(fields.to, "test2@foo1.invalid"); + Assert.equal(fields.cc, "test3@foo1.invalid"); + Assert.equal(fields.bcc, "test4@foo1.invalid"); + + // Test - expandMailingLists with plain text. + + checkPopulate("test4@foo.invalid", "test4@foo.invalid"); + + // Test - expandMailingLists with html. + + checkPopulate("test5@foo.invalid", "test5@foo.invalid"); + + // Test - expandMailingLists with a list of three items. + + checkPopulate( + "TestList1 <TestList1>", + "test1@foo.invalid,test2@foo.invalid,test3@foo.invalid" + ); + + // Test - expandMailingLists with a list of one item. + + checkPopulate("TestList2 <TestList2>", "test4@foo.invalid"); + + checkPopulate("TestList3 <TestList3>", "test5@foo.invalid"); + + // Test - expandMailingLists with items from multiple address books. + + checkPopulate( + "TestList1 <TestList1>, test3@com.invalid", + "test1@foo.invalid,test2@foo.invalid,test3@foo.invalid,test3@com.invalid" + ); + + checkPopulate( + "TestList2 <TestList2>, ListTest2 <ListTest2>", + "test4@foo.invalid,test4@com.invalid" + ); + + checkPopulate( + "TestList3 <TestList3>, ListTest1 <ListTest1>", + "test5@foo.invalid,test1@com.invalid,test2@com.invalid,test3@com.invalid" + ); + + // test bug 254519 rfc 2047 encoding + checkPopulate( + "=?iso-8859-1?Q?Sure=F6name=2C_Forename_Dr=2E?= <pb@bieringer.invalid>", + '"Sure\u00F6name, Forename Dr." <pb@bieringer.invalid>' + ); +} diff --git a/comm/mailnews/compose/test/unit/test_nsMsgCompose2.js b/comm/mailnews/compose/test/unit/test_nsMsgCompose2.js new file mode 100644 index 0000000000..5f234444b8 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_nsMsgCompose2.js @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for nsMsgCompose functions relating to send listeners. + */ + +let gMsgCompose = null; +let numSendListenerFunctions = 7; + +let gSLAll = new Array(numSendListenerFunctions + 1); + +function sendListener() {} + +sendListener.prototype = { + mReceived: 0, + mAutoRemoveItem: 0, + + onStartSending(aMsgID, aMsgSize) { + this.mReceived |= 0x01; + if (this.mAutoRemoveItem == 0x01) { + gMsgCompose.removeMsgSendListener(this); + } + }, + onProgress(aMsgID, aProgress, aProgressMax) { + this.mReceived |= 0x02; + if (this.mAutoRemoveItem == 0x02) { + gMsgCompose.removeMsgSendListener(this); + } + }, + onStatus(aMsgID, aMsg) { + this.mReceived |= 0x04; + if (this.mAutoRemoveItem == 0x04) { + gMsgCompose.removeMsgSendListener(this); + } + }, + onStopSending(aMsgID, aStatus, aMsg, aReturnFile) { + this.mReceived |= 0x08; + if (this.mAutoRemoveItem == 0x08) { + gMsgCompose.removeMsgSendListener(this); + } + }, + onGetDraftFolderURI(aMsgID, aFolderURI) { + this.mReceived |= 0x10; + if (this.mAutoRemoveItem == 0x10) { + gMsgCompose.removeMsgSendListener(this); + } + }, + onSendNotPerformed(aMsgID, aStatus) { + this.mReceived |= 0x20; + if (this.mAutoRemoveItem == 0x20) { + gMsgCompose.removeMsgSendListener(this); + } + }, + onTransportSecurityError(msgID, status, secInfo, location) { + this.mReceived |= 0x40; + if (this.mAutoRemoveItem == 0x40) { + gMsgCompose.removeMsgSendListener(this); + } + }, +}; + +function NotifySendListeners() { + gMsgCompose.onStartSending(null, null); + gMsgCompose.onProgress(null, null, null); + gMsgCompose.onStatus(null, null); + gMsgCompose.onStopSending(null, null, null, null); + gMsgCompose.onGetDraftFolderURI(null, null); + gMsgCompose.onSendNotPerformed(null, null); + gMsgCompose.onTransportSecurityError(null, null, null, ""); +} + +function run_test() { + gMsgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance( + Ci.nsIMsgCompose + ); + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + gMsgCompose.initialize(params); + + Assert.ok(gMsgCompose != null); + + // Test - Add a listener + + for (let i = 0; i < numSendListenerFunctions + 1; ++i) { + gSLAll[i] = new sendListener(); + gMsgCompose.addMsgSendListener(gSLAll[i]); + } + + // Test - Notify all listeners + + NotifySendListeners(); + + const bitMask = (1 << numSendListenerFunctions) - 1; + for (let i = 0; i < numSendListenerFunctions + 1; ++i) { + Assert.equal(gSLAll[i].mReceived, bitMask); + gSLAll[i].mReceived = 0; + + // And prepare for test 3. + gSLAll[i].mAutoRemoveItem = 1 << i; + } + + // Test - Remove some listeners as we go + + NotifySendListeners(); + + let currentReceived = 0; + + for (let i = 0; i < numSendListenerFunctions + 1; ++i) { + if (i < numSendListenerFunctions) { + currentReceived += 1 << i; + } + + Assert.equal(gSLAll[i].mReceived, currentReceived); + gSLAll[i].mReceived = 0; + } + + // Test - Ensure the listeners have been removed. + + NotifySendListeners(); + + for (let i = 0; i < numSendListenerFunctions + 1; ++i) { + if (i < numSendListenerFunctions) { + Assert.equal(gSLAll[i].mReceived, 0); + } else { + Assert.equal(gSLAll[i].mReceived, bitMask); + } + } + + // Test - Remove main listener + + gMsgCompose.removeMsgSendListener(gSLAll[numSendListenerFunctions]); +} diff --git a/comm/mailnews/compose/test/unit/test_nsMsgCompose3.js b/comm/mailnews/compose/test/unit/test_nsMsgCompose3.js new file mode 100644 index 0000000000..71521abff4 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_nsMsgCompose3.js @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for increasing the popularity of contacts via + * expandMailingLists. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var TESTS = [ + { + email: "em@test.invalid", + // TB 2 stored popularity as hex, so we need to check correct handling. + prePopularity: "a", + postPopularity: "11", + }, + { + email: "e@test.invalid", + prePopularity: "0", + postPopularity: "1", + }, + { + email: "e@test.invalid", + prePopularity: "1", + postPopularity: "2", + }, + { + email: "em@test.invalid", + prePopularity: "11", + postPopularity: "12", + }, +]; + +function checkPopulate(aTo, aCheckTo) { + let msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance( + Ci.nsIMsgCompose + ); + + // Set up some basic fields for compose. + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + fields.to = aTo; + + // Set up some params + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + + params.composeFields = fields; + + msgCompose.initialize(params); + + Assert.ok(!msgCompose.expandMailingLists()); + + Assert.equal(fields.to, aCheckTo); +} + +function run_test() { + loadABFile("../../../data/tb2hexpopularity", kPABData.fileName); + + // Check the popularity index on a couple of cards. + let AB = MailServices.ab.getDirectory(kPABData.URI); + + for (let i = 0; i < TESTS.length; ++i) { + let card = AB.cardForEmailAddress(TESTS[i].email); + Assert.ok(!!card); + + // Thunderbird 2 stored its popularityIndexes as hex, hence when we read it + // now we're going to get a hex value. The AB has a value of "a". + Assert.equal( + card.getProperty("PopularityIndex", -1), + TESTS[i].prePopularity + ); + + // Call the check populate function. + checkPopulate(TESTS[i].email, TESTS[i].email); + + // Now we've run check populate, check the popularityIndex has increased. + card = AB.cardForEmailAddress(TESTS[i].email); + Assert.ok(!!card); + + // Thunderbird 2 stored its popularityIndexes as hex, hence when we read it + // now we're going to get a hex value. The AB has a value of "a". + Assert.equal( + card.getProperty("PopularityIndex", -1), + TESTS[i].postPopularity + ); + } +} diff --git a/comm/mailnews/compose/test/unit/test_nsSmtpService1.js b/comm/mailnews/compose/test/unit/test_nsSmtpService1.js new file mode 100644 index 0000000000..d062ef3f1e --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_nsSmtpService1.js @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for nsSmtpService + */ + +var SmtpServiceContractID = "@mozilla.org/messengercompose/smtp;1"; +var nsISmtpService = Ci.nsISmtpService; + +function run_test() { + var smtpService = Cc[SmtpServiceContractID].getService(nsISmtpService); + + // Test - no servers + + var smtpServers = smtpService.servers; + Assert.equal(smtpServers.length, 0); + + Assert.equal(smtpService.defaultServer, null); + + // Test - add single server, and check + + var smtpServer = smtpService.createServer(); + + smtpServer.hostname = "localhost"; + smtpServer.description = "test"; + + smtpService.defaultServer = smtpServer; + + // Test - Check to see there is only one element in the server list + smtpServers = smtpService.servers; + Assert.ok(smtpServers.length == 1); + + // Test - Find the server in different ways + Assert.equal(smtpServer, smtpService.findServer("", "localhost")); + Assert.equal(smtpServer, smtpService.getServerByKey(smtpServer.key)); + + // Test - Try finding one that doesn't exist. + Assert.equal(null, smtpService.findServer("", "test")); + + // Test - Check default server is still ok + Assert.equal(smtpServer, smtpService.defaultServer); + + // Test - Delete the only server + smtpService.deleteServer(smtpServer); + + smtpServers = smtpService.servers; + Assert.ok(smtpServers.length == 0); + + // do_check_eq(null, smtpService.defaultServer); + + // Test - add multiple servers + + var smtpServerArray = new Array(3); + + for (let i = 0; i < 3; ++i) { + smtpServerArray[i] = smtpService.createServer(); + } + + smtpServerArray[0].hostname = "localhost"; + smtpServerArray[0].description = "test"; + smtpServerArray[0].username = "user"; + + smtpServerArray[1].hostname = "localhost"; + smtpServerArray[1].description = "test1"; + smtpServerArray[1].username = "user1"; + + smtpServerArray[2].hostname = "localhost1"; + smtpServerArray[2].description = "test2"; + smtpServerArray[2].username = ""; + + // Now check them + smtpServers = smtpService.servers; + + var found = [false, false, false]; + + for (smtpServer of smtpServers) { + for (let i = 0; i < 3; ++i) { + if (smtpServer.key == smtpServerArray[i].key) { + found[i] = true; + } + } + } + + Assert.equal(found, "true,true,true"); + + // Test - Find the servers. + + Assert.equal( + smtpServerArray[0].key, + smtpService.findServer("user", "localhost").key + ); + Assert.equal( + smtpServerArray[1].key, + smtpService.findServer("user1", "localhost").key + ); + Assert.equal( + smtpServerArray[2].key, + smtpService.findServer("", "localhost1").key + ); + + Assert.equal(null, smtpService.findServer("user2", "localhost")); + + // XXX: FIXME + // do_check_eq(null, smtpService.findServer("", "localhost")); + + for (let i = 0; i < 3; ++i) { + Assert.equal( + smtpServerArray[i].key, + smtpService.getServerByKey(smtpServerArray[i].key).key + ); + } + + smtpService.defaultServer = smtpServerArray[2]; + Assert.equal( + smtpService.defaultServer.key, + smtpServerArray[2].key, + "Default server should be correctly set" + ); + + // Test - Delete the servers + + for (let i = 0; i < 3; ++i) { + smtpService.deleteServer(smtpServerArray[i]); + } + + smtpServers = smtpService.servers; + Assert.ok(smtpServers.length == 0); +} diff --git a/comm/mailnews/compose/test/unit/test_saveDraft.js b/comm/mailnews/compose/test/unit/test_saveDraft.js new file mode 100644 index 0000000000..b3f7029bab --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_saveDraft.js @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for checking correctly saved as draft with unread. + */ + +add_task(async function checkDraft() { + await createMessage(); + Assert.equal(gDraftFolder.getTotalMessages(false), 1); + Assert.equal(gDraftFolder.getNumUnread(false), 1); +}); + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + run_next_test(); +} diff --git a/comm/mailnews/compose/test/unit/test_sendBackground.js b/comm/mailnews/compose/test/unit/test_sendBackground.js new file mode 100644 index 0000000000..6d0a59f4f9 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_sendBackground.js @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Tests sending a message in the background (checks auto-send works). + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var server; +var originalData; +var finished = false; +var identity = null; +var testFile1 = do_get_file("data/429891_testcase.eml"); +var testFile2 = do_get_file("data/message1.eml"); + +var kTestFile1Sender = "from_A@foo.invalid"; +var kTestFile1Recipient = "to_A@foo.invalid"; + +var kIdentityMail = "identity@foo.invalid"; + +var gMsgSendLater; + +// This listener handles the post-sending of the actual message and checks the +// sequence and ensures the data is correct. +function msll() {} + +msll.prototype = { + _initialTotal: 0, + + // nsIMsgSendLaterListener + onStartSending(aTotal) { + this._initialTotal = 1; + Assert.equal(gMsgSendLater.sendingMessages, true); + Assert.equal(aTotal, 1); + }, + onMessageStartSending( + aCurrentMessage, + aTotalMessageCount, + aMessageHeader, + aIdentity + ) {}, + onMessageSendProgress( + aCurrentMessage, + aTotalMessageCount, + aMessageSendPercent, + aMessageCopyPercent + ) {}, + onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) { + do_throw( + "onMessageSendError should not have been called, status: " + aStatus + ); + }, + onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) { + do_test_finished(); + print("msll onStopSending\n"); + try { + Assert.equal(aStatus, 0); + Assert.equal(aTotalTried, 1); + Assert.equal(aSuccessful, 1); + Assert.equal(this._initialTotal, 1); + Assert.equal(gMsgSendLater.sendingMessages, false); + + do_check_transaction(server.playTransaction(), [ + "EHLO test", + "MAIL FROM:<" + + kTestFile1Sender + + "> BODY=8BITMIME SIZE=" + + originalData.length, + "RCPT TO:<" + kTestFile1Recipient + ">", + "DATA", + ]); + + // Compare data file to what the server received + Assert.equal(originalData, server._daemon.post); + + // check there's still one message left in the folder + Assert.equal( + gMsgSendLater.getUnsentMessagesFolder(null).getTotalMessages(false), + 1 + ); + + finished = true; + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } + }, +}; + +add_task(async function run_the_test() { + // The point of this test - send in background. + Services.prefs.setBoolPref("mailnews.sendInBackground", true); + + // Ensure we have a local mail account, an normal account and appropriate + // servers and identities. + localAccountUtils.loadLocalMailAccount(); + + // Now load (and internally initialize) the send later service + gMsgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService( + Ci.nsIMsgSendLater + ); + + // Test file - for bug 429891 + originalData = await IOUtils.readUTF8(testFile1.path); + + // Check that the send later service thinks we don't have messages to send + Assert.equal(gMsgSendLater.hasUnsentMessages(identity), false); + + MailServices.accounts.setSpecialFolders(); + + let account = MailServices.accounts.createAccount(); + let incomingServer = MailServices.accounts.createIncomingServer( + "test", + "localhost", + "pop3" + ); + + // Start the fake SMTP server + server = setupServerDaemon(); + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + identity = getSmtpIdentity(kIdentityMail, smtpServer); + + account.addIdentity(identity); + account.defaultIdentity = identity; + account.incomingServer = incomingServer; + MailServices.accounts.defaultAccount = account; + + localAccountUtils.rootFolder.createLocalSubfolder("Sent"); + + Assert.equal(identity.doFcc, true); + + // Now prepare to actually "send" the message later, i.e. dump it in the + // unsent messages folder. + + var compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + // Setting the compFields sender and recipient to any value is required to + // survive mime_sanity_check_fields in nsMsgCompUtils.cpp. + // Sender and recipient are required for sendMessageFile but SMTP + // transaction values will be used directly from mail body. + compFields.from = "irrelevant@foo.invalid"; + compFields.to = "irrelevant@foo.invalid"; + + var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + var msgSend2 = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // A test to check that we are sending files correctly, including checking + // what the server receives and what we output. + test = "sendMessageLater"; + + var messageListener = new msll(); + + gMsgSendLater.addListener(messageListener); + + // Send this message later - it shouldn't get sent + msgSend.sendMessageFile( + identity, + "", + compFields, + testFile2, + false, + false, + Ci.nsIMsgSend.nsMsgQueueForLater, + null, + null, + null, + null + ); + + // Send the unsent message in the background, because we have + // mailnews.sendInBackground set, nsMsgSendLater should just send it for + // us. + msgSend2.sendMessageFile( + identity, + "", + compFields, + testFile1, + false, + false, + Ci.nsIMsgSend.nsMsgDeliverBackground, + null, + null, + null, + null + ); + + server.performTest(); + + do_timeout(10000, function () { + if (!finished) { + do_throw("Notifications of message send/copy not received"); + } + }); + + do_test_pending(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +}); diff --git a/comm/mailnews/compose/test/unit/test_sendMailAddressIDN.js b/comm/mailnews/compose/test/unit/test_sendMailAddressIDN.js new file mode 100644 index 0000000000..56ab77c303 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_sendMailAddressIDN.js @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Tests sending messages to addresses with non-ASCII characters. + */ +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +load("../../../resources/alertTestUtils.js"); + +var test = null; +var server; +var finished = false; + +var sentFolder; + +var kSender = "from@foo.invalid"; +var kToASCII = "to@foo.invalid"; +var kToValid = "to@v\u00E4lid.foo.invalid"; +var kToValidACE = "to@xn--vlid-loa.foo.invalid"; +var kToInvalid = "b\u00F8rken.to@invalid.foo.invalid"; +var kToInvalidWithoutDomain = "b\u00F8rken.to"; +var NS_ERROR_ILLEGAL_LOCALPART = 0x80553139; + +// for alertTestUtils.js +let resolveAlert; +function alertPS(parent, aDialogText, aText) { + var composeProps = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties" + ); + var expectedAlertMessage = + composeProps.GetStringFromName("sendFailed") + + "\n" + + composeProps + .GetStringFromName("errorIllegalLocalPart2") + // Without the domain, we currently don't display any name in the + // message part. + .replace("%s", test == kToInvalidWithoutDomain ? "" : test); + + // we should only get here for the kToInvalid test case + Assert.equal(aText, expectedAlertMessage); + resolveAlert(); +} + +// message listener implementations +function MsgSendListener(aRecipient, originalData) { + this.rcpt = aRecipient; + this.originalData = originalData; +} + +/** + * @implements {nsIMsgSendListener} + * @implements {nsIMsgCopyServiceListener} + */ +MsgSendListener.prototype = { + // nsIMsgSendListener + onStartSending(aMsgID, aMsgSize) {}, + onProgress(aMsgID, aProgress, aProgressMax) {}, + onStatus(aMsgID, aMsg) {}, + onStopSending(aMsgID, aStatus, aMsg, aReturnFile) { + try { + if (test == kToValid || test == kToASCII) { + Assert.equal(aStatus, 0); + do_check_transaction(server.playTransaction(), [ + "EHLO test", + "MAIL FROM:<" + + kSender + + "> BODY=8BITMIME SIZE=" + + this.originalData.length, + "RCPT TO:<" + this.rcpt + ">", + "DATA", + ]); + // Compare data file to what the server received + Assert.equal(this.originalData, server._daemon.post); + } else { + Assert.equal(aStatus, NS_ERROR_ILLEGAL_LOCALPART); + do_check_transaction(server.playTransaction(), ["EHLO test"]); + // Local address (before the @) has non-ascii char(s) or the @ is + // missing from the address. An alert is triggered after the EHLO is + // sent. Nothing else occurs so we "finish" the test to avoid + // NS_ERROR_ABORT test failure due to timeout waiting for the send + // (which doesn't occurs) to complete. + } + } catch (e) { + do_throw(e); + } finally { + server.stop(); + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(false); + } + do_test_finished(); + } + }, + onGetDraftFolderURI(aMsgID, aFolderURI) {}, + onSendNotPerformed(aMsgID, aStatus) {}, + onTransportSecurityError(msgID, status, secInfo, location) {}, + + // nsIMsgCopyServiceListener + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) {}, + GetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + Assert.equal(aStatus, 0); + try { + // Now do a comparison of what is in the sent mail folder + let msgData = mailTestUtils.loadMessageToString( + sentFolder, + mailTestUtils.firstMsgHdr(sentFolder) + ); + // Skip the headers etc that mailnews adds + var pos = msgData.indexOf("From:"); + Assert.notEqual(pos, -1); + msgData = msgData.substr(pos); + Assert.equal(this.originalData, msgData); + } catch (e) { + do_throw(e); + } finally { + finished = true; + } + }, + + // QueryInterface + QueryInterface: ChromeUtils.generateQI([ + "nsIMsgSendListener", + "nsIMsgCopyServiceListener", + ]), +}; + +async function doSendTest(aRecipient, aRecipientExpected, waitForPrompt) { + info(`Testing send to ${aRecipient} will get sent to ${aRecipientExpected}`); + let promiseAlertReceived = new Promise(resolve => { + resolveAlert = resolve; + }); + test = aRecipient; + server = setupServerDaemon(); + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kSender, smtpServer); + Assert.equal(identity.doFcc, true); + + // Random test file with data we don't actually care about. ;-) + var testFile = do_get_file("data/message1.eml"); + var originalData = await IOUtils.readUTF8(testFile.path); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + do_test_pending(); + var compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + compFields.from = identity.email; + compFields.to = aRecipient; + + var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + msgSend.sendMessageFile( + identity, + "", + compFields, + testFile, + false, + false, + Ci.nsIMsgSend.nsMsgDeliverNow, + null, + new MsgSendListener(aRecipientExpected, originalData), + null, + null + ); + + server.performTest(); + do_timeout(10000, function () { + if (!finished) { + do_throw("Notifications of message send/copy not received"); + } + }); + if (waitForPrompt) { + await promiseAlertReceived; + } + } catch (e) { + Assert.ok(false, "Send fail: " + e); + } finally { + server.stop(); + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +add_setup(function () { + registerAlertTestUtils(); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + MailServices.accounts.setSpecialFolders(); + sentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent"); +}); + +add_task(async function plainASCIIRecipient() { + // Test 1: + // Plain ASCII recipient address. + await doSendTest(kToASCII, kToASCII, false); +}); + +add_task(async function domainContainsNonAscii() { + // Test 2: + // The recipient's domain part contains a non-ASCII character, hence the + // address needs to be converted to ACE before sending. + // The old code would just strip the non-ASCII character and try to send + // the message to the remaining - wrong! - address. + // The new code will translate the domain part to ACE for the SMTP + // transaction (only), i.e. the To: header will stay as stated by the sender. + await doSendTest(kToValid, kToValidACE, false); +}); + +add_task(async function localContainsNonAscii() { + // Test 3: + // The recipient's local part contains a non-ASCII character, which is not + // allowed with unextended SMTP. + // The old code would just strip the invalid character and try to send the + // message to the remaining - wrong! - address. + // The new code will present an informational message box and deny sending. + await doSendTest(kToInvalid, kToInvalid, true); +}); + +add_task(async function invalidCharNoAt() { + // Test 4: + // Bug 856506. invalid char without '@' causes crash. + await doSendTest(kToInvalidWithoutDomain, kToInvalidWithoutDomain, true); +}); diff --git a/comm/mailnews/compose/test/unit/test_sendMailMessage.js b/comm/mailnews/compose/test/unit/test_sendMailMessage.js new file mode 100644 index 0000000000..ea294a0b92 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_sendMailMessage.js @@ -0,0 +1,189 @@ +/** + * Protocol tests for SMTP. + * + * This test currently consists of verifying the correct protocol sequence + * between mailnews and SMTP server. It does not check the data of the message + * either side of the link, it will be extended later to do that. + */ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var server; + +var kIdentityMail = "identity@foo.invalid"; +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; +var kUsername = "testsmtp"; +var kPassword = "smtptest"; + +async function test_RFC2821() { + // Test file + var testFile = do_get_file("data/message1.eml"); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kIdentityMail, smtpServer); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // Just a basic test to check we're sending mail correctly. + test = "Basic sendMailMessage"; + + // First do test with identity email address used for smtp MAIL FROM. + Services.prefs.setBoolPref("mail.smtp.useSenderForSmtpMailFrom", false); + + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + + await urlListener.promise; + + var transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "MAIL FROM:<" + kIdentityMail + "> BODY=8BITMIME SIZE=159", + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + + smtpServer.closeCachedConnections(); + server.resetTest(); + + // Now do the same test with sender's email address used for smtp MAIL FROM. + Services.prefs.setBoolPref("mail.smtp.useSenderForSmtpMailFrom", true); + + urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + + await urlListener.promise; + + transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159", + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + + smtpServer.closeCachedConnections(); + server.resetTest(); + + // This time with auth. + test = "Auth sendMailMessage"; + + smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext; + smtpServer.socketType = Ci.nsMsgSocketType.plain; + smtpServer.username = kUsername; + smtpServer.password = kPassword; + + // First do test with identity email address used for smtp MAIL FROM. + Services.prefs.setBoolPref("mail.smtp.useSenderForSmtpMailFrom", false); + + urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + + await urlListener.promise; + + transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPassword), + "MAIL FROM:<" + kIdentityMail + "> BODY=8BITMIME SIZE=159", + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + + smtpServer.closeCachedConnections(); + server.resetTest(); + + // Now do the same test with sender's email address used for smtp MAIL FROM. + Services.prefs.setBoolPref("mail.smtp.useSenderForSmtpMailFrom", true); + + urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + + await urlListener.promise; + + transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPassword), + "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159", + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +add_task(async function run() { + server = setupServerDaemon(); + await test_RFC2821(); +}); diff --git a/comm/mailnews/compose/test/unit/test_sendMessageFile.js b/comm/mailnews/compose/test/unit/test_sendMessageFile.js new file mode 100644 index 0000000000..cb2882e88f --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_sendMessageFile.js @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Protocol tests for SMTP. + * + * This test verifies: + * - Sending a message to an SMTP server (which is also covered elsewhere). + * - Correct reception of the message by the SMTP server. + * - Correct saving of the message to the sent folder. + * + * Originally written to test bug 429891 where saving to the sent folder was + * mangling the message. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var server; +var sentFolder; +var originalData; +var finished = false; + +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; + +function msl() {} + +msl.prototype = { + // nsIMsgSendListener + onStartSending(aMsgID, aMsgSize) {}, + onProgress(aMsgID, aProgress, aProgressMax) {}, + onStatus(aMsgID, aMsg) {}, + onStopSending(aMsgID, aStatus, aMsg, aReturnFile) { + try { + Assert.equal(aStatus, 0); + + do_check_transaction(server.playTransaction(), [ + "EHLO test", + "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=" + originalData.length, + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + + // Compare data file to what the server received + Assert.equal(originalData, server._daemon.post); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(false); + } + } + }, + onGetDraftFolderURI(aMsgID, aFolderURI) {}, + onSendNotPerformed(aMsgID, aStatus) {}, + onTransportSecurityError(msgID, status, secInfo, location) {}, + + // nsIMsgCopyServiceListener + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) {}, + GetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + Assert.equal(aStatus, 0); + try { + // Now do a comparison of what is in the sent mail folder + let msgData = mailTestUtils.loadMessageToString( + sentFolder, + mailTestUtils.firstMsgHdr(sentFolder) + ); + + // Skip the headers etc that mailnews adds + var pos = msgData.indexOf("From:"); + Assert.notEqual(pos, -1); + + msgData = msgData.substr(pos); + + Assert.equal(originalData, msgData); + } catch (e) { + do_throw(e); + } finally { + finished = true; + do_test_finished(); + } + }, + + // QueryInterface + QueryInterface: ChromeUtils.generateQI([ + "nsIMsgSendListener", + "nsIMsgCopyServiceListener", + ]), +}; + +add_task(async function run_the_test() { + server = setupServerDaemon(); + + // Test file - for bug 429891 + var testFile = do_get_file("data/429891_testcase.eml"); + originalData = await IOUtils.readUTF8(testFile.path); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + MailServices.accounts.setSpecialFolders(); + + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kSender, smtpServer); + + sentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent"); + + Assert.equal(identity.doFcc, true); + + var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // A test to check that we are sending files correctly, including checking + // what the server receives and what we output. + test = "sendMessageFile"; + + // Msg Comp Fields + + var compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + compFields.from = identity.email; + compFields.to = kTo; + + var messageListener = new msl(); + + msgSend.sendMessageFile( + identity, + "", + compFields, + testFile, + false, + false, + Ci.nsIMsgSend.nsMsgDeliverNow, + null, + messageListener, + null, + null + ); + + server.performTest(); + + do_timeout(10000, function () { + if (!finished) { + do_throw("Notifications of message send/copy not received"); + } + }); + + do_test_pending(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +}); diff --git a/comm/mailnews/compose/test/unit/test_sendMessageLater.js b/comm/mailnews/compose/test/unit/test_sendMessageLater.js new file mode 100644 index 0000000000..7dcaf8ec32 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_sendMessageLater.js @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Protocol tests for SMTP. + * + * This test verifies: + * - Sending a message to an SMTP server (which is also covered elsewhere). + * - Correct reception of the message by the SMTP server. + * - Correct saving of the message to the sent folder. + * + * Originally written to test bug 429891 where saving to the sent folder was + * mangling the message. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var server; +var smtpServer; +var originalData; +var finished = false; +var identity = null; +var testFile = do_get_file("data/429891_testcase.eml"); +var kTestFileSender = "from_A@foo.invalid"; +var kTestFileRecipient = "to_A@foo.invalid"; + +var kIdentityMail = "identity@foo.invalid"; + +var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService( + Ci.nsIMsgSendLater +); + +// This listener handles the post-sending of the actual message and checks the +// sequence and ensures the data is correct. +function msll() {} + +msll.prototype = { + _initialTotal: 0, + _startedSending: false, + + // nsIMsgSendLaterListener + onStartSending(aTotalMessageCount) { + this._initialTotal = 1; + Assert.equal(msgSendLater.sendingMessages, true); + }, + onMessageStartSending( + aCurrentMessage, + aTotalMessageCount, + aMessageHeader, + aIdentity + ) { + this._startedSending = true; + }, + onMessageSendProgress( + aCurrentMessage, + aTotalMessageCount, + aMessageSendPercent, + aMessageCopyPercent + ) { + // XXX Enable this function + }, + onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) { + do_throw( + "onMessageSendError should not have been called, status: " + aStatus + ); + }, + onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) { + do_test_finished(); + print("msll onStopSending\n"); + try { + Assert.equal(this._startedSending, true); + Assert.equal(aStatus, 0); + Assert.equal(aTotalTried, 1); + Assert.equal(aSuccessful, 1); + Assert.equal(this._initialTotal, 1); + Assert.equal(msgSendLater.sendingMessages, false); + + do_check_transaction(server.playTransaction(), [ + "EHLO test", + "MAIL FROM:<" + + kTestFileSender + + "> BODY=8BITMIME SIZE=" + + originalData.length, + "RCPT TO:<" + kTestFileRecipient + ">", + "DATA", + ]); + + // Compare data file to what the server received + Assert.equal(originalData, server._daemon.post); + + finished = true; + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } + }, +}; + +/* exported OnStopCopy */ +// for head_compose.js +function OnStopCopy(aStatus) { + dump("OnStopCopy()\n"); + + try { + Assert.equal(aStatus, 0); + + // Check this is false before we start sending + Assert.equal(msgSendLater.sendingMessages, false); + + let folder = msgSendLater.getUnsentMessagesFolder(identity); + + // Check we have a message in the unsent message folder + Assert.equal(folder.getTotalMessages(false), 1); + + // Check that the send later service thinks we have messages to send + Assert.equal(msgSendLater.hasUnsentMessages(identity), true); + + // Now do a comparison of what is in the sent mail folder + let msgData = mailTestUtils.loadMessageToString( + folder, + mailTestUtils.firstMsgHdr(folder) + ); + // Skip the headers etc that mailnews adds + var pos = msgData.indexOf("From:"); + Assert.notEqual(pos, -1); + + msgData = msgData.substr(pos); + + // Check the data is matching. + Assert.equal(originalData, msgData); + + sendMessageLater(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + finished = true; + } +} + +// This function does the actual send later +function sendMessageLater() { + // Set up the SMTP server. + server = setupServerDaemon(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // Start the fake SMTP server + server.start(); + smtpServer.port = server.port; + + // A test to check that we are sending files correctly, including checking + // what the server receives and what we output. + test = "sendMessageLater"; + + var messageListener = new msll(); + + msgSendLater.addListener(messageListener); + + // Send the unsent message + msgSendLater.sendUnsentMessages(identity); + + server.performTest(); + + do_timeout(10000, function () { + if (!finished) { + do_throw("Notifications of message send/copy not received"); + } + }); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +add_task(async function run_the_test() { + // Test file - for bug 429891 + originalData = await IOUtils.readUTF8(testFile.path); + + // Ensure we have a local mail account, an normal account and appropriate + // servers and identities. + localAccountUtils.loadLocalMailAccount(); + + // Check that the send later service thinks we don't have messages to send + Assert.equal(msgSendLater.hasUnsentMessages(identity), false); + + MailServices.accounts.setSpecialFolders(); + + let account = MailServices.accounts.createAccount(); + let incomingServer = MailServices.accounts.createIncomingServer( + "test", + "localhost", + "pop3" + ); + + smtpServer = getBasicSmtpServer(1); + identity = getSmtpIdentity(kIdentityMail, smtpServer); + + account.addIdentity(identity); + account.defaultIdentity = identity; + account.incomingServer = incomingServer; + MailServices.accounts.defaultAccount = account; + + localAccountUtils.rootFolder.createLocalSubfolder("Sent"); + + Assert.equal(identity.doFcc, true); + + // Now prepare to actually "send" the message later, i.e. dump it in the + // unsent messages folder. + + var compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + // Setting the compFields sender and recipient to any value is required to + // survive mime_sanity_check_fields in nsMsgCompUtils.cpp. + // Sender and recipient are required for sendMessageFile but SMTP + // transaction values will be used directly from mail body. + compFields.from = "irrelevant@foo.invalid"; + compFields.to = "irrelevant@foo.invalid"; + + var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + + msgSend.sendMessageFile( + identity, + "", + compFields, + testFile, + false, + false, + Ci.nsIMsgSend.nsMsgQueueForLater, + null, + copyListener, + null, + null + ); + + // Now we wait till we get copy notification of completion. + do_test_pending(); +}); diff --git a/comm/mailnews/compose/test/unit/test_sendMessageLater2.js b/comm/mailnews/compose/test/unit/test_sendMessageLater2.js new file mode 100644 index 0000000000..bd0b974400 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_sendMessageLater2.js @@ -0,0 +1,301 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Complex test for the send message later function - including sending multiple + * times in the same session. + * + * XXX: This test is intended to additionally test sending of multiple messages + * from one send later instance, however due to the fact we use one connection + * per message sent, it is very difficult to consistently get the fake server + * reconnected in time for the next connection. Thus, sending of multiple + * messages is currently disabled (but commented out for local testing if + * required), when we fix bug 136871 we should be able to enable the multiple + * messages option. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); +var { PromiseUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseUtils.sys.mjs" +); + +var server = null; +var smtpServer; +var gSentFolder; +var identity = null; +var gMsgFile = [ + do_get_file("data/message1.eml"), + do_get_file("data/429891_testcase.eml"), +]; +var kTestFileSender = ["from_B@foo.invalid", "from_A@foo.invalid"]; +var kTestFileRecipient = ["to_B@foo.invalid", "to_A@foo.invalid"]; + +var gMsgFileData = []; +var gMsgOrder = []; +var gLastSentMessage = 0; + +var kIdentityMail = "identity@foo.invalid"; + +var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService( + Ci.nsIMsgSendLater +); + +var messageListener; +var onStopCopyPromise = PromiseUtils.defer(); + +/* exported OnStopCopy */ +// for head_compose.js +// This function is used to find out when the copying of the message to the +// unsent message folder is completed, and hence can fire off the actual +// sending of the message. +function OnStopCopy(aStatus) { + Assert.equal(aStatus, 0); + + // Check this is false before we start sending. + Assert.equal(msgSendLater.sendingMessages, false); + + // Check that the send later service thinks we have messages to send. + Assert.equal(msgSendLater.hasUnsentMessages(identity), true); + + // Check we have a message in the unsent message folder. + Assert.equal(gSentFolder.getTotalMessages(false), gMsgOrder.length); + + // Start the next step after a brief time so that functions can finish + // properly. + onStopCopyPromise.resolve(); +} + +add_setup(async function () { + // Load in the test files so we have a record of length and their data. + for (var i = 0; i < gMsgFile.length; ++i) { + gMsgFileData[i] = await IOUtils.readUTF8(gMsgFile[i].path); + } + + // Ensure we have a local mail account, an normal account and appropriate + // servers and identities. + localAccountUtils.loadLocalMailAccount(); + + // Check that the send later service thinks we don't have messages to send. + Assert.equal(msgSendLater.hasUnsentMessages(identity), false); + + MailServices.accounts.setSpecialFolders(); + + let account = MailServices.accounts.createAccount(); + let incomingServer = MailServices.accounts.createIncomingServer( + "test", + "localhost", + "pop3" + ); + + smtpServer = getBasicSmtpServer(1); + identity = getSmtpIdentity(kIdentityMail, smtpServer); + + account.addIdentity(identity); + account.defaultIdentity = identity; + account.incomingServer = incomingServer; + MailServices.accounts.defaultAccount = account; + + localAccountUtils.rootFolder.createLocalSubfolder("Sent"); + + gSentFolder = msgSendLater.getUnsentMessagesFolder(identity); + + // Don't copy messages to sent folder for this test. + identity.doFcc = false; + + // Create and add a listener. + messageListener = new MsgSendLaterListener(); + + msgSendLater.addListener(messageListener); + + // Set up the server. + server = setupServerDaemon(); + server.setDebugLevel(fsDebugRecv); +}); + +add_task(async function test_sendMessageLater2_message1() { + // Copy Message from file to folder. + await sendMessageLater(0); + + // Send unsent message. + await sendUnsentMessages(); + + // Check sent folder is now empty. + Assert.equal(gSentFolder.getTotalMessages(false), 0); + + // Reset the server. + server.stop(); + server.resetTest(); + + // Reset counts. + resetCounts(); +}); + +add_task(async function test_sendMessageLater2_429891_testcase() { + // Copy more messages. + await sendMessageLater(1); + + // XXX Only do one the second time round, as described at the start of the + // file. + // await sendMessageLater(0); + + // Test send again. + await sendUnsentMessages(); +}); + +async function sendMessageLater(aTestFileIndex) { + gMsgOrder.push(aTestFileIndex); + + // Prepare to actually "send" the message later, i.e. dump it in the + // unsent messages folder. + + var compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + // Setting the compFields sender and recipient to any value is required to + // survive mime_sanity_check_fields in nsMsgCompUtils.cpp. + // Sender and recipient are required for sendMessageFile but SMTP + // transaction values will be used directly from mail body. + compFields.from = "irrelevant@foo.invalid"; + compFields.to = "irrelevant@foo.invalid"; + + var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + + msgSend.sendMessageFile( + identity, + "", + compFields, + gMsgFile[aTestFileIndex], + false, + false, + Ci.nsIMsgSend.nsMsgQueueForLater, + null, + copyListener, + null, + null + ); + await onStopCopyPromise.promise; + // Reset onStopCopyPromise. + onStopCopyPromise = PromiseUtils.defer(); +} + +function resetCounts() { + gMsgOrder = []; + gLastSentMessage = 0; +} + +// This function does the actual send later. +async function sendUnsentMessages() { + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // Start the fake SMTP server. + server.start(); + smtpServer.port = server.port; + + // Send the unsent message. + msgSendLater.sendUnsentMessages(identity); + } catch (e) { + throw new Error(e); + } + await messageListener.promise; + messageListener.deferPromise(); +} + +// This listener handles the post-sending of the actual message and checks the +// sequence and ensures the data is correct. +class MsgSendLaterListener { + constructor() { + this._deferredPromise = PromiseUtils.defer(); + } + + checkMessageSend(aCurrentMessage) { + do_check_transaction(server.playTransaction(), [ + "EHLO test", + "MAIL FROM:<" + + kTestFileSender[gMsgOrder[aCurrentMessage - 1]] + + "> BODY=8BITMIME SIZE=" + + gMsgFileData[gMsgOrder[aCurrentMessage - 1]].length, + "RCPT TO:<" + kTestFileRecipient[gMsgOrder[aCurrentMessage - 1]] + ">", + "DATA", + ]); + + // Compare data file to what the server received. + Assert.equal( + gMsgFileData[gMsgOrder[aCurrentMessage - 1]], + server._daemon.post + ); + } + + // nsIMsgSendLaterListener + onStartSending(aTotalMessageCount) { + Assert.equal(aTotalMessageCount, gMsgOrder.length); + Assert.equal(msgSendLater.sendingMessages, true); + } + onMessageStartSending( + aCurrentMessage, + aTotalMessageCount, + aMessageHeader, + aIdentity + ) { + if (gLastSentMessage > 0) { + this.checkMessageSend(aCurrentMessage); + } + Assert.equal(gLastSentMessage + 1, aCurrentMessage); + gLastSentMessage = aCurrentMessage; + } + onMessageSendProgress( + aCurrentMessage, + aTotalMessageCount, + aMessageSendPercent, + aMessageCopyPercent + ) { + Assert.equal(aTotalMessageCount, gMsgOrder.length); + Assert.equal(gLastSentMessage, aCurrentMessage); + Assert.equal(msgSendLater.sendingMessages, true); + } + onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) { + throw new Error( + "onMessageSendError should not have been called, status: " + aStatus + ); + } + onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) { + try { + Assert.equal(aStatus, 0); + Assert.equal(aTotalTried, aSuccessful); + Assert.equal(msgSendLater.sendingMessages, false); + + // Check that the send later service now thinks we don't have messages to + // send. + Assert.equal(msgSendLater.hasUnsentMessages(identity), false); + + this.checkMessageSend(gLastSentMessage); + } catch (e) { + throw new Error(e); + } + // The extra timeout here is to work around an issue where sometimes + // the sendUnsentMessages is completely synchronous up until onStopSending + // and sometimes it isn't. This protects us for the synchronous case to + // allow the sendUnsentMessages function to complete and exit before we + // resolve the promise. + PromiseTestUtils.promiseDelay(0).then(resolve => { + this._deferredPromise.resolve(true); + }); + } + + deferPromise() { + this._deferredPromise = PromiseUtils.defer(); + } + + get promise() { + return this._deferredPromise.promise; + } +} diff --git a/comm/mailnews/compose/test/unit/test_sendMessageLater3.js b/comm/mailnews/compose/test/unit/test_sendMessageLater3.js new file mode 100644 index 0000000000..08e32481c6 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_sendMessageLater3.js @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Protocol tests for SMTP. + * + * For trying to send a message later with no server connected, this test + * verifies: + * - A correct status response. + * - A correct state at the end of attempting to send. + */ + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +load("../../../resources/alertTestUtils.js"); + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var originalData; +var identity = null; +var testFile = do_get_file("data/429891_testcase.eml"); + +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; + +var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService( + Ci.nsIMsgSendLater +); + +// for alertTestUtils.js +function alertPS(parent, aDialogTitle, aText) { + dump("Hiding Alert {\n" + aText + "\n} End Alert\n"); +} + +// This listener handles the post-sending of the actual message and checks the +// sequence and ensures the data is correct. +function msll() {} + +msll.prototype = { + _initialTotal: 0, + _errorRaised: false, + + // nsIMsgSendLaterListener + onStartSending(aTotal) { + this._initialTotal = 1; + Assert.equal(msgSendLater.sendingMessages, true); + }, + onMessageStartSending( + aCurrentMessage, + aTotalMessageCount, + aMessageHeader, + aIdentity + ) {}, + onMessageSendProgress( + aCurrentMessage, + aTotalMessageCount, + aMessageSendPercent, + aMessageCopyPercent + ) {}, + onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) { + this._errorRaised = true; + }, + onStopSending(aStatus, aMsg, aTotal, aSuccessful) { + print("msll onStopSending\n"); + + // NS_ERROR_SMTP_SEND_FAILED_REFUSED is 2153066798 + Assert.equal(aStatus, 2153066798); + Assert.equal(aTotal, 1); + Assert.equal(aSuccessful, 0); + Assert.equal(this._initialTotal, 1); + Assert.equal(this._errorRaised, true); + Assert.equal(msgSendLater.sendingMessages, false); + // Check that the send later service still thinks we have messages to send. + Assert.equal(msgSendLater.hasUnsentMessages(identity), true); + + do_test_finished(); + }, +}; + +/* exported OnStopCopy */ +// for head_compose.js +function OnStopCopy(aStatus) { + Assert.equal(aStatus, 0); + + // Check this is false before we start sending + Assert.equal(msgSendLater.sendingMessages, false); + + let folder = msgSendLater.getUnsentMessagesFolder(identity); + + // Check that the send later service thinks we have messages to send. + Assert.equal(msgSendLater.hasUnsentMessages(identity), true); + + // Check we have a message in the unsent message folder + Assert.equal(folder.getTotalMessages(false), 1); + + // Now do a comparison of what is in the unsent mail folder + let msgData = mailTestUtils.loadMessageToString( + folder, + mailTestUtils.firstMsgHdr(folder) + ); + + // Skip the headers etc that mailnews adds + var pos = msgData.indexOf("From:"); + Assert.notEqual(pos, -1); + + msgData = msgData.substr(pos); + + // Check the data is matching. + Assert.equal(originalData, msgData); + + do_timeout(0, sendMessageLater); +} + +// This function does the actual send later +function sendMessageLater() { + // No server for this test, just attempt to send unsent and wait. + var messageListener = new msll(); + + msgSendLater.addListener(messageListener); + + // Send the unsent message + msgSendLater.sendUnsentMessages(identity); +} + +add_task(async function run_the_test() { + registerAlertTestUtils(); + + // Test file - for bug 429891 + originalData = await IOUtils.readUTF8(testFile.path); + + // Ensure we have a local mail account, an normal account and appropriate + // servers and identities. + localAccountUtils.loadLocalMailAccount(); + + // Check that the send later service thinks we don't have messages to send. + Assert.equal(msgSendLater.hasUnsentMessages(identity), false); + + MailServices.accounts.setSpecialFolders(); + + let account = MailServices.accounts.createAccount(); + let incomingServer = MailServices.accounts.createIncomingServer( + "test", + "localhost", + "pop3" + ); + + var smtpServer = getBasicSmtpServer(); + identity = getSmtpIdentity(kSender, smtpServer); + + account.addIdentity(identity); + account.defaultIdentity = identity; + account.incomingServer = incomingServer; + MailServices.accounts.defaultAccount = account; + + localAccountUtils.rootFolder.createLocalSubfolder("Sent"); + + identity.doFcc = false; + + // Now prepare to actually "send" the message later, i.e. dump it in the + // unsent messages folder. + + var compFields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + compFields.from = identity.email; + compFields.to = kTo; + + var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( + Ci.nsIMsgSend + ); + + msgSend.sendMessageFile( + identity, + "", + compFields, + testFile, + false, + false, + Ci.nsIMsgSend.nsMsgQueueForLater, + null, + copyListener, + null, + null + ); + + // Now we wait till we get copy notification of completion. + do_test_pending(); +}); diff --git a/comm/mailnews/compose/test/unit/test_sendObserver.js b/comm/mailnews/compose/test/unit/test_sendObserver.js new file mode 100644 index 0000000000..3640d1ca02 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_sendObserver.js @@ -0,0 +1,52 @@ +/* + * Tests that the mail-set-sender observer, used by extensions to modify the + * outgoing server, works. + * + * This is adapted from test_messageHeaders.js + */ + +var CompFields = CC( + "@mozilla.org/messengercompose/composefields;1", + Ci.nsIMsgCompFields +); + +// nsIObserver implementation. +var gData = ""; +var observer = { + observe(aSubject, aTopic, aData) { + if (aTopic == "mail-set-sender") { + Assert.ok(aSubject instanceof Ci.nsIMsgCompose); + gData = aData; + } + }, +}; + +add_task(async function testObserver() { + let fields = new CompFields(); + let identity = getSmtpIdentity( + "from@tinderbox.invalid", + getBasicSmtpServer() + ); + identity.fullName = "Observer Tester"; + fields.to = "Emile <nobody@tinderbox.invalid>"; + fields.cc = "Alex <alex@tinderbox.invalid>"; + fields.subject = "Let's test the observer"; + + await richCreateMessage(fields, [], identity); + // observer data should have: + // (no account), Ci.nsIMsgSend.nsMsgSaveAsDraft, identity.key + Assert.equal(gData, ",4,id1"); + + // Now try with an account + await richCreateMessage(fields, [], identity, localAccountUtils.msgAccount); + // observer data should have: + // (local account key), Ci.nsIMsgSend.nsMsgSaveAsDraft, identity.key + Assert.equal(gData, "account1,4,id1"); +}); + +function run_test() { + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + Services.obs.addObserver(observer, "mail-set-sender"); + run_next_test(); +} diff --git a/comm/mailnews/compose/test/unit/test_smtp8bitMime.js b/comm/mailnews/compose/test/unit/test_smtp8bitMime.js new file mode 100644 index 0000000000..d763947154 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtp8bitMime.js @@ -0,0 +1,105 @@ +/** + * 8BITMIME tests for SMTP. + * + * This test verifies that 8BITMIME is sent to the server only if the server + * advertises it AND if mail.strictly_mime doesn't force us to send 7bit. + * It does not check the data of the message on either side of the link. + */ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var server; + +var kIdentityMail = "identity@foo.invalid"; +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; + +// aStrictMime: Test if mail.strictly_mime omits the BODY=8BITMIME attribute. +// aServer8bit: Test if BODY=8BITMIME is only sent if advertised by the server. + +async function test_8bitmime(aStrictMime, aServer8bit) { + // Test file + var testFile = do_get_file("data/message1.eml"); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kIdentityMail, smtpServer); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + test = + "Strictly MIME" + + (aStrictMime ? "on (7bit" : "off (8bit") + + ", 8BITMIME " + + (aServer8bit ? "" : "not ") + + "advertised)"; + + Services.prefs.setBoolPref("mail.strictly_mime", aStrictMime); + + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + + await urlListener.promise; + + var transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "MAIL FROM:<" + + kSender + + (!aStrictMime && aServer8bit + ? "> BODY=8BITMIME SIZE=159" + : "> SIZE=159"), + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + + server.resetTest(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +add_task(async function run() { + // The default SMTP server advertises 8BITMIME capability. + server = setupServerDaemon(); + await test_8bitmime(true, true); + await test_8bitmime(false, true); + + // Now we need a server which does not advertise 8BITMIME capability. + function createHandler(d) { + var handler = new SMTP_RFC2821_handler(d); + handler.kCapabilities = ["SIZE"]; + return handler; + } + server = setupServerDaemon(createHandler); + await test_8bitmime(true, false); + await test_8bitmime(false, false); +}); diff --git a/comm/mailnews/compose/test/unit/test_smtpAuthMethods.js b/comm/mailnews/compose/test/unit/test_smtpAuthMethods.js new file mode 100644 index 0000000000..24d5c6d554 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpAuthMethods.js @@ -0,0 +1,166 @@ +/** + * Authentication tests for SMTP. + * + * Test code <copied from="test_pop3AuthMethods.js"> + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var server; +var kAuthSchemes; +var smtpServer; +var testFile; +var identity; + +var kUsername = "fred"; +var kPassword = "wilma"; +var kIdentityMail = "identity@foo.invalid"; +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; +var MAILFROM = "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159"; +var RCPTTO = "RCPT TO:<" + kTo + ">"; +var AUTHPLAIN = "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPassword); + +var tests = [ + { + title: + "Cleartext password, with server supporting AUTH PLAIN, LOGIN, and CRAM", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext, + serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"], + expectSuccess: true, + transaction: ["EHLO test", AUTHPLAIN, MAILFROM, RCPTTO, "DATA"], + }, + { + title: "Cleartext password, with server only supporting AUTH LOGIN", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext, + serverAuthMethods: ["LOGIN"], + expectSuccess: true, + transaction: ["EHLO test", "AUTH LOGIN", MAILFROM, RCPTTO, "DATA"], + }, + { + title: + "Encrypted password, with server supporting AUTH PLAIN, LOGIN and CRAM", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted, + serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"], + expectSuccess: true, + transaction: ["EHLO test", "AUTH CRAM-MD5", MAILFROM, RCPTTO, "DATA"], + }, + { + title: + "Encrypted password, with server only supporting AUTH PLAIN (must fail)", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted, + serverAuthMethods: ["PLAIN"], + expectSuccess: false, + transaction: ["EHLO test"], + }, + { + title: + "Any secure method, with server supporting AUTH PLAIN, LOGIN and CRAM", + clientAuthMethod: Ci.nsMsgAuthMethod.secure, + serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"], + expectSuccess: true, + transaction: ["EHLO test", "AUTH CRAM-MD5", MAILFROM, RCPTTO, "DATA"], + }, + { + title: + "Any secure method, with server only supporting AUTH PLAIN (must fail)", + clientAuthMethod: Ci.nsMsgAuthMethod.secure, + serverAuthMethods: ["PLAIN"], + expectSuccess: false, + transaction: ["EHLO test"], + }, +]; + +function nextTest() { + if (tests.length == 0) { + // this is sync, so we run into endTest() at the end of run_test() now + return; + } + server.resetTest(); + + var curTest = tests.shift(); + test = curTest.title; + dump("NEXT test: " + curTest.title + "\n"); + + // Adapt to curTest + kAuthSchemes = curTest.serverAuthMethods; + smtpServer.authMethod = curTest.clientAuthMethod; + + // Run test + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + let resolved = false; + urlListener.promise.catch(e => {}).finally(() => (resolved = true)); + Services.tm.spinEventLoopUntil("wait for sending", () => resolved); + + do_check_transaction(server.playTransaction(), curTest.transaction); + + smtpServer.closeCachedConnections(); + nextTest(); +} + +function run_test() { + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + function createHandler(d) { + var handler = new SMTP_RFC2821_handler(d); + handler.kUsername = kUsername; + handler.kPassword = kPassword; + handler.kAuthRequired = true; + handler.kAuthSchemes = kAuthSchemes; + return handler; + } + server = setupServerDaemon(createHandler); + dump("AUTH PLAIN = " + AUTHPLAIN + "\n"); + server.start(); + + localAccountUtils.loadLocalMailAccount(); + smtpServer = getBasicSmtpServer(server.port); + smtpServer.socketType = Ci.nsMsgSocketType.plain; + smtpServer.username = kUsername; + smtpServer.password = kPassword; + identity = getSmtpIdentity(kIdentityMail, smtpServer); + + testFile = do_get_file("data/message1.eml"); + + nextTest(); + } catch (e) { + do_throw(e); + } finally { + endTest(); + } +} + +function endTest() { + dump("endTest()\n"); + server.stop(); + + dump("emptying event loop\n"); + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + dump("next event\n"); + thread.processNextEvent(true); + } +} diff --git a/comm/mailnews/compose/test/unit/test_smtpClient.js b/comm/mailnews/compose/test/unit/test_smtpClient.js new file mode 100644 index 0000000000..b06ec48560 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpClient.js @@ -0,0 +1,136 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +let server = setupServerDaemon(); +server.start(); +registerCleanupFunction(() => { + server.stop(); +}); + +/** + * Test sending is aborted when alwaysSTARTTLS is set, but the server doesn't + * support STARTTLS. + */ +add_task(async function testAbort() { + server.resetTest(); + let smtpServer = getBasicSmtpServer(server.port); + let identity = getSmtpIdentity("identity@foo.invalid", smtpServer); + // Set to always use STARTTLS. + smtpServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS; + + do_test_pending(); + + let urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, status) { + // Test sending is aborted with NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS. + Assert.equal(status, 0x80553126); + do_test_finished(); + }, + }; + + // Send a message. + let testFile = do_get_file("data/message1.eml"); + MailServices.smtp.sendMailMessage( + testFile, + "to@foo.invalid", + identity, + "from@foo.invalid", + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + server.performTest(); +}); + +/** + * Test client identity extension works. + */ +add_task(async function testClientIdentityExtension() { + server.resetTest(); + let smtpServer = getBasicSmtpServer(server.port); + let identity = getSmtpIdentity("identity@foo.invalid", smtpServer); + // Enable and set clientid to the smtp server. + smtpServer.clientidEnabled = true; + smtpServer.clientid = "uuid-111"; + + // Send a message. + let asyncUrlListener = new PromiseTestUtils.PromiseUrlListener(); + let testFile = do_get_file("data/message1.eml"); + MailServices.smtp.sendMailMessage( + testFile, + "to@foo.invalid", + identity, + "from@foo.invalid", + null, + asyncUrlListener, + null, + null, + false, + "", + {}, + {} + ); + + await asyncUrlListener.promise; + + // Check CLIENTID command is sent. + let transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "CLIENTID UUID uuid-111", + "MAIL FROM:<from@foo.invalid> BODY=8BITMIME SIZE=159", + "RCPT TO:<to@foo.invalid>", + "DATA", + ]); +}); + +/** + * Test that when To and Cc/Bcc contain the same address, should send only + * one RCPT TO per address. + */ +add_task(async function testDeduplicateRecipients() { + server.resetTest(); + let smtpServer = getBasicSmtpServer(server.port); + let identity = getSmtpIdentity("identity@foo.invalid", smtpServer); + + // Send a message, notice to1 appears twice in the recipients argument. + let asyncUrlListener = new PromiseTestUtils.PromiseUrlListener(); + let testFile = do_get_file("data/message1.eml"); + MailServices.smtp.sendMailMessage( + testFile, + "to1@foo.invalid,to2@foo.invalid,to1@foo.invalid", + identity, + "from@foo.invalid", + null, + asyncUrlListener, + null, + null, + false, + "", + {}, + {} + ); + + await asyncUrlListener.promise; + + // Check only one RCPT TO is sent for to1. + let transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "MAIL FROM:<from@foo.invalid> BODY=8BITMIME SIZE=159", + "RCPT TO:<to1@foo.invalid>", + "RCPT TO:<to2@foo.invalid>", + "DATA", + ]); +}); diff --git a/comm/mailnews/compose/test/unit/test_smtpPassword.js b/comm/mailnews/compose/test/unit/test_smtpPassword.js new file mode 100644 index 0000000000..f4b8515df7 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpPassword.js @@ -0,0 +1,97 @@ +/** + * Authentication tests for SMTP. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/passwordStorage.js"); + +var server; + +var kIdentityMail = "identity@foo.invalid"; +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; +var kUsername = "testsmtp"; +// Password needs to match the login information stored in the signons json +// file. +var kPassword = "smtptest"; + +add_task(async function () { + function createHandler(d) { + var handler = new SMTP_RFC2821_handler(d); + // Username needs to match the login information stored in the signons json + // file. + handler.kUsername = kUsername; + handler.kPassword = kPassword; + handler.kAuthRequired = true; + return handler; + } + server = setupServerDaemon(createHandler); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8.json"); + + // Test file + var testFile = do_get_file("data/message1.eml"); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // Start the fake SMTP server + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kIdentityMail, smtpServer); + + // This time with auth + test = "Auth sendMailMessage"; + + smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext; + smtpServer.socketType = Ci.nsMsgSocketType.plain; + smtpServer.username = kUsername; + + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + + await urlListener.promise; + + var transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kPassword), + "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159", + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +}); diff --git a/comm/mailnews/compose/test/unit/test_smtpPassword2.js b/comm/mailnews/compose/test/unit/test_smtpPassword2.js new file mode 100644 index 0000000000..a0445ad0a3 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpPassword2.js @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Extra tests for SMTP passwords (forgetPassword) + */ + +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/passwordStorage.js"); + +var kUser1 = "testsmtp"; +var kUser2 = "testsmtpa"; +var kProtocol = "smtp"; +var kHostname = "localhost"; +var kServerUrl = kProtocol + "://" + kHostname; + +add_task(async function () { + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8-multiple.json"); + + // Set up the basic accounts and folders. + localAccountUtils.loadLocalMailAccount(); + + var smtpServer1 = getBasicSmtpServer(); + var smtpServer2 = getBasicSmtpServer(); + + smtpServer1.authMethod = 3; + smtpServer1.username = kUser1; + smtpServer2.authMethod = 3; + smtpServer2.username = kUser2; + + // Test - Check there are two logins to begin with. + let logins = Services.logins.findLogins(kServerUrl, null, kServerUrl); + + Assert.equal(logins.length, 2); + + // These will either be one way around or the other. + if (logins[0].username == kUser1) { + Assert.equal(logins[1].username, kUser2); + } else { + Assert.equal(logins[0].username, kUser2); + Assert.equal(logins[1].username, kUser1); + } + + // Test - Remove a login via the incoming server + smtpServer1.forgetPassword(); + + logins = Services.logins.findLogins(kServerUrl, null, kServerUrl); + + // should be one login left for kUser2 + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUser2); + + // Test - Remove the other login via the incoming server + smtpServer2.forgetPassword(); + + logins = Services.logins.findLogins(kServerUrl, null, kServerUrl); + + // There should be no login left. + Assert.equal(logins.length, 0); +}); diff --git a/comm/mailnews/compose/test/unit/test_smtpPasswordFailure1.js b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure1.js new file mode 100644 index 0000000000..b7d7f1ef43 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure1.js @@ -0,0 +1,151 @@ +/** + * This test checks to see if the smtp password failure is handled correctly. + * The steps are: + * - Have an invalid password in the password database. + * - Check we get a prompt asking what to do. + * - Check retry does what it should do. + * - Check cancel does what it should do. + * + * XXX Due to problems with the fakeserver + smtp not using one connection for + * multiple sends, the rest of this test is in test_smtpPasswordFailure2.js. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/alertTestUtils.js"); +load("../../../resources/passwordStorage.js"); + +var server; +var attempt = 0; + +var kIdentityMail = "identity@foo.invalid"; +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; +var kUsername = "testsmtp"; +// Login information needs to match the login information stored in the signons +// json file. +var kInvalidPassword = "smtptest"; +var kValidPassword = "smtptest1"; + +/* exported alert, confirmEx */ +// for alertTestUtils.js +function alert(aDialogText, aText) { + // The first few attempts may prompt about the password problem, the last + // attempt shouldn't. + Assert.ok(attempt < 4); + + // Log the fact we've got an alert, but we don't need to test anything here. + dump("Alert Title: " + aDialogText + "\nAlert Text: " + aText + "\n"); +} + +function confirmExPS( + parent, + aDialogTitle, + aText, + aButtonFlags, + aButton0Title, + aButton1Title, + aButton2Title, + aCheckMsg, + aCheckState +) { + switch (++attempt) { + // First attempt, retry. + case 1: + dump("\nAttempting retry\n"); + return 0; + // Second attempt, cancel. + case 2: + dump("\nCancelling login attempt\n"); + return 1; + default: + do_throw("unexpected attempt number " + attempt); + return 1; + } +} + +add_task(async function () { + function createHandler(d) { + var handler = new SMTP_RFC2821_handler(d); + // Username needs to match the login information stored in the signons json + // file. + handler.kUsername = kUsername; + handler.kPassword = kValidPassword; + handler.kAuthRequired = true; + return handler; + } + server = setupServerDaemon(createHandler); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8.json"); + + registerAlertTestUtils(); + + // Test file + var testFile = do_get_file("data/message1.eml"); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kIdentityMail, smtpServer); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // This time with auth + test = "Auth sendMailMessage"; + + smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext; + smtpServer.socketType = Ci.nsMsgSocketType.plain; + smtpServer.username = kUsername; + + dump("Send\n"); + + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + null, + null, + null, + false, + "", + {}, + {} + ); + + server.performTest(); + + dump("End Send\n"); + + Assert.equal(attempt, 2); + + // Check that we haven't forgetton the login even though we've retried and cancelled. + let logins = Services.logins.findLogins( + "smtp://localhost", + null, + "smtp://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUsername); + Assert.equal(logins[0].password, kInvalidPassword); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +}); diff --git a/comm/mailnews/compose/test/unit/test_smtpPasswordFailure2.js b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure2.js new file mode 100644 index 0000000000..f394db434d --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure2.js @@ -0,0 +1,178 @@ +/** + * This test checks to see if the pop3 password failure is handled correctly. + * The steps are: + * - Have an invalid password in the password database. + * - Re-initiate connection, this time select enter new password, check that + * we get a new password prompt and can enter the password. + * + * XXX Due to problems with the fakeserver + smtp not using one connection for + * multiple sends, the first part of this test is in + * test_smtpPasswordFailure2.js. + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/alertTestUtils.js"); +load("../../../resources/passwordStorage.js"); + +var server; +var attempt = 0; + +var kIdentityMail = "identity@foo.invalid"; +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; +var kUsername = "testsmtp"; +// Password needs to match the login information stored in the signons json +// file. +var kInvalidPassword = "smtptest"; +var kValidPassword = "smtptest1"; + +function confirmExPS( + aDialogTitle, + aText, + aButtonFlags, + aButton0Title, + aButton1Title, + aButton2Title, + aCheckMsg, + aCheckState +) { + switch (++attempt) { + // First attempt, retry. + case 1: + dump("\nAttempting Retry\n"); + return 0; + // Second attempt, enter a new password. + case 2: + dump("\nEnter new password\n"); + return 2; + default: + do_throw("unexpected attempt number " + attempt); + return 1; + } +} + +function promptPasswordPS( + aParent, + aDialogTitle, + aText, + aPassword, + aCheckMsg, + aCheckState +) { + if (attempt == 2) { + aPassword.value = kValidPassword; + aCheckState.value = true; + return true; + } + return false; +} + +add_task(async function () { + function createHandler(d) { + var handler = new SMTP_RFC2821_handler(d); + // Username needs to match the login information stored in the signons json + // file. + handler.kUsername = kUsername; + handler.kPassword = kValidPassword; + handler.kAuthRequired = true; + handler.kAuthSchemes = ["PLAIN", "LOGIN"]; // make match expected transaction below + return handler; + } + server = setupServerDaemon(createHandler); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8.json"); + + registerAlertTestUtils(); + + // Test file + var testFile = do_get_file("data/message1.eml"); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + // Start the fake SMTP server + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kIdentityMail, smtpServer); + + // This time with auth + test = "Auth sendMailMessage"; + + smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext; + smtpServer.socketType = Ci.nsMsgSocketType.plain; + smtpServer.username = kUsername; + + dump("Send\n"); + + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + + await urlListener.promise; + + dump("End Send\n"); + + Assert.equal(attempt, 2); + + var transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "EHLO test", + // attempt 3 invalid password + "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kInvalidPassword), + "AUTH LOGIN", + // attempt 4 which retries + "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kInvalidPassword), + "AUTH LOGIN", + // then we enter the correct password + "AUTH PLAIN " + AuthPLAIN.encodeLine(kUsername, kValidPassword), + "MAIL FROM:<" + kSender + "> BODY=8BITMIME SIZE=159", + "RCPT TO:<" + kTo + ">", + "DATA", + ]); + + // Now check the new one has been saved. + let logins = Services.logins.findLogins( + "smtp://localhost", + null, + "smtp://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUsername); + Assert.equal(logins[0].password, kValidPassword); + do_test_finished(); + } catch (e) { + do_throw(e); + } finally { + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +}); diff --git a/comm/mailnews/compose/test/unit/test_smtpPasswordFailure3.js b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure3.js new file mode 100644 index 0000000000..27312b47a4 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpPasswordFailure3.js @@ -0,0 +1,154 @@ +/** + * This test checks to see if the smtp password failure is handled correctly + * when the server drops the connection on an authentication error. + * The steps are: + * - Have an invalid password in the password database. + * - Re-initiate connection, this time select enter new password, check that + * we get a new password prompt and can enter the password. + * + */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/alertTestUtils.js"); +load("../../../resources/passwordStorage.js"); + +var server; +var attempt = 0; + +var kIdentityMail = "identity@foo.invalid"; +var kSender = "from@foo.invalid"; +var kTo = "to@foo.invalid"; +var kUsername = "testsmtp"; +// Password needs to match the login information stored in the signons json +// file. +var kValidPassword = "smtptest1"; + +function confirmExPS( + aDialogTitle, + aText, + aButtonFlags, + aButton0Title, + aButton1Title, + aButton2Title, + aCheckMsg, + aCheckState +) { + switch (++attempt) { + // First attempt, retry. + case 1: + dump("\nAttempting Retry\n"); + return 0; + // Second attempt, enter a new password. + case 2: + dump("\nEnter new password\n"); + return 2; + default: + do_throw("unexpected attempt number " + attempt); + return 1; + } +} + +function promptPasswordPS( + aParent, + aDialogTitle, + aText, + aPassword, + aCheckMsg, + aCheckState +) { + if (attempt == 2) { + aPassword.value = kValidPassword; + aCheckState.value = true; + return true; + } + return false; +} + +add_task(async function () { + function createHandler(d) { + var handler = new SMTP_RFC2821_handler(d); + handler.dropOnAuthFailure = true; + // Username needs to match the login information stored in the signons json + // file. + handler.kUsername = kUsername; + handler.kPassword = kValidPassword; + handler.kAuthRequired = true; + handler.kAuthSchemes = ["PLAIN", "LOGIN"]; // make match expected transaction below + return handler; + } + server = setupServerDaemon(createHandler); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8.json"); + + registerAlertTestUtils(); + + // Test file + var testFile = do_get_file("data/message1.eml"); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + // Start the fake SMTP server + server.start(); + var smtpServer = getBasicSmtpServer(server.port); + var identity = getSmtpIdentity(kIdentityMail, smtpServer); + + // This time with auth + test = "Auth sendMailMessage"; + + smtpServer.authMethod = Ci.nsMsgAuthMethod.passwordCleartext; + smtpServer.socketType = Ci.nsMsgSocketType.plain; + smtpServer.username = kUsername; + + do_test_pending(); + + MailServices.smtp.sendMailMessage( + testFile, + kTo, + identity, + kSender, + null, + URLListener, + null, + null, + false, + "", + {}, + {} + ); + + server.performTest(); +}); + +var URLListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, rc) { + // Check for ok status. + Assert.equal(rc, 0); + // Now check the new password has been saved. + let logins = Services.logins.findLogins( + "smtp://localhost", + null, + "smtp://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUsername); + Assert.equal(logins[0].password, kValidPassword); + + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); + }, +}; diff --git a/comm/mailnews/compose/test/unit/test_smtpProtocols.js b/comm/mailnews/compose/test/unit/test_smtpProtocols.js new file mode 100644 index 0000000000..bba7d55b6b --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpProtocols.js @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for getting smtp urls via the protocol handler. + */ + +var defaultProtocolFlags = + Ci.nsIProtocolHandler.URI_NORELATIVE | + Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD | + Ci.nsIProtocolHandler.URI_NON_PERSISTABLE | + Ci.nsIProtocolHandler.ALLOWS_PROXY | + Ci.nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT; + +var protocols = [ + { + protocol: "smtp", + urlSpec: "smtp://user@localhost/", + defaultPort: Ci.nsISmtpUrl.DEFAULT_SMTP_PORT, + }, + { + protocol: "smtps", + urlSpec: "smtps://user@localhost/", + defaultPort: Ci.nsISmtpUrl.DEFAULT_SMTPS_PORT, + }, +]; + +function run_test() { + for (var part = 0; part < protocols.length; ++part) { + print("protocol: " + protocols[part].protocol); + + var pH = Cc[ + "@mozilla.org/network/protocol;1?name=" + protocols[part].protocol + ].createInstance(Ci.nsIProtocolHandler); + + Assert.equal(pH.scheme, protocols[part].protocol); + Assert.equal( + Services.io.getDefaultPort(pH.scheme), + protocols[part].defaultPort + ); + Assert.equal(Services.io.getProtocolFlags(pH.scheme), defaultProtocolFlags); + + // Whip through some of the ports to check we get the right results. + for (let i = 0; i < 1024; ++i) { + Assert.equal(pH.allowPort(i, ""), i == protocols[part].defaultPort); + } + + // Check we get a URI when we ask for one + var uri = Services.io.newURI(protocols[part].urlSpec); + + uri.QueryInterface(Ci.nsISmtpUrl); + + Assert.equal(uri.spec, protocols[part].urlSpec); + + try { + // This call should throw NS_ERROR_NOT_IMPLEMENTED. If it doesn't, + // then we should implement a new test for it. + pH.newChannel(uri, null); + // If it didn't throw, then shout about it. + do_throw("newChannel not throwing NS_ERROR_NOT_IMPLEMENTED."); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_NOT_IMPLEMENTED); + } + } +} diff --git a/comm/mailnews/compose/test/unit/test_smtpProxy.js b/comm/mailnews/compose/test/unit/test_smtpProxy.js new file mode 100644 index 0000000000..7a008be001 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpProxy.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +// Tests that SMTP over a SOCKS proxy works. + +const { NetworkTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/NetworkTestUtils.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +const PORT = 25; +var daemon, localserver, server; + +add_setup(function () { + localAccountUtils.loadLocalMailAccount(); + server = setupServerDaemon(); + daemon = server._daemon; + server.start(); + NetworkTestUtils.configureProxy("smtp.tinderbox.invalid", PORT, server.port); + localserver = getBasicSmtpServer(PORT, "smtp.tinderbox.invalid"); +}); + +add_task(async function sendMessage() { + equal(daemon.post, undefined); + let identity = getSmtpIdentity("test@tinderbox.invalid", localserver); + var testFile = do_get_file("data/message1.eml"); + var urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.smtp.sendMailMessage( + testFile, + "somebody@example.org", + identity, + "me@example.org", + null, + urlListener, + null, + null, + false, + "", + {}, + {} + ); + await urlListener.promise; + notEqual(daemon.post, ""); +}); + +add_task(async function cleanUp() { + NetworkTestUtils.shutdownServers(); +}); diff --git a/comm/mailnews/compose/test/unit/test_smtpServer.js b/comm/mailnews/compose/test/unit/test_smtpServer.js new file mode 100644 index 0000000000..5e252a44f0 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpServer.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests for nsISmtpServer implementation. + */ + +/** + * Test that cached server password is cleared when password storage changed. + */ +add_task(async function test_passwordmgr_change() { + // Create an nsISmtpServer instance and set a password. + let server = Cc["@mozilla.org/messenger/smtp/server;1"].createInstance( + Ci.nsISmtpServer + ); + server.password = "smtp-pass"; + equal(server.password, "smtp-pass", "Password should be cached."); + + // Trigger the change event of password manager. + Services.logins.setLoginSavingEnabled("smtp://localhost", false); + equal(server.password, "", "Password should be cleared."); +}); + +/** + * Test getter/setter of attributes. + */ +add_task(async function test_attributes() { + // Create an nsISmtpServer instance and set a password. + let server = Cc["@mozilla.org/messenger/smtp/server;1"].createInstance( + Ci.nsISmtpServer + ); + + server.description = "アイウ"; + equal(server.description, "アイウ", "Description should be correctly set."); + + server.hostname = "サービス.jp"; + equal(server.hostname, "サービス.jp", "Hostname should be correctly set."); +}); + +/** + * Tests the UID attribute of servers. + */ +add_task(async function testUID() { + const UUID_REGEXP = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; + + // Create a server and check it the UID is set when accessed. + + let serverA = MailServices.smtp.createServer(); + Assert.stringMatches( + serverA.UID, + UUID_REGEXP, + "server A's UID should exist and be a UUID" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.smtpserver.${serverA.key}.uid`), + serverA.UID, + "server A's UID should be saved to the preferences" + ); + Assert.throws( + () => (serverA.UID = "00001111-2222-3333-4444-555566667777"), + /NS_ERROR_ABORT/, + "server A's UID should be unchangeable after it is set" + ); + + // Create a second server and check the two UIDs don't match. + + let serverB = MailServices.smtp.createServer(); + Assert.stringMatches( + serverB.UID, + UUID_REGEXP, + "server B's UID should exist and be a UUID" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.smtpserver.${serverB.key}.uid`), + serverB.UID, + "server B's UID should be saved to the preferences" + ); + Assert.notEqual( + serverB.UID, + serverA.UID, + "server B's UID should not be the same as server A's" + ); + + // Create a third server and set the UID before it is accessed. + + let serverC = MailServices.smtp.createServer(); + serverC.UID = "11112222-3333-4444-5555-666677778888"; + Assert.equal( + serverC.UID, + "11112222-3333-4444-5555-666677778888", + "server C's UID set correctly" + ); + Assert.equal( + Services.prefs.getStringPref(`mail.smtpserver.${serverC.key}.uid`), + "11112222-3333-4444-5555-666677778888", + "server C's UID should be saved to the preferences" + ); + Assert.throws( + () => (serverC.UID = "22223333-4444-5555-6666-777788889999"), + /NS_ERROR_ABORT/, + "server C's UID should be unchangeable after it is set" + ); +}); diff --git a/comm/mailnews/compose/test/unit/test_smtpURL.js b/comm/mailnews/compose/test/unit/test_smtpURL.js new file mode 100644 index 0000000000..833ca91817 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_smtpURL.js @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for checking SMTP URLs are working as expected. + * XXX this test needs extending as we fix up nsSmtpUrl. + */ + +var smtpURLs = [ + { + url: "smtp://user@localhost/", + spec: "smtp://user@localhost/", + username: "user", + }, + { + url: "smtps://user@localhost/", + spec: "smtps://user@localhost/", + username: "user", + }, +]; + +function run_test() { + var url; + for (var part = 0; part < smtpURLs.length; ++part) { + print("url: " + smtpURLs[part].url); + + url = Services.io.newURI(smtpURLs[part].url); + + Assert.equal(url.spec, smtpURLs[part].spec); + Assert.equal(url.username, smtpURLs[part].username); + } +} diff --git a/comm/mailnews/compose/test/unit/test_splitRecipients.js b/comm/mailnews/compose/test/unit/test_splitRecipients.js new file mode 100644 index 0000000000..b51da1c7a2 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_splitRecipients.js @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for nsMsgCompFields functions. + * Currently only tests nsIMsgCompFields::SplitRecipients + */ + +var splitRecipientsTests = [ + { + recipients: "me@foo.invalid", + emailAddressOnly: false, + count: 1, + result: ["me@foo.invalid"], + }, + { + recipients: "me@foo.invalid, me2@foo.invalid", + emailAddressOnly: false, + count: 2, + result: ["me@foo.invalid", "me2@foo.invalid"], + }, + { + recipients: '"foo bar" <me@foo.invalid>', + emailAddressOnly: false, + count: 1, + result: ["foo bar <me@foo.invalid>"], + }, + { + recipients: '"foo bar" <me@foo.invalid>', + emailAddressOnly: true, + count: 1, + result: ["me@foo.invalid"], + }, + { + recipients: '"foo bar" <me@foo.invalid>, "bar foo" <me2@foo.invalid>', + emailAddressOnly: false, + count: 2, + result: ["foo bar <me@foo.invalid>", "bar foo <me2@foo.invalid>"], + }, + { + recipients: '"foo bar" <me@foo.invalid>, "bar foo" <me2@foo.invalid>', + emailAddressOnly: true, + count: 2, + result: ["me@foo.invalid", "me2@foo.invalid"], + }, + { + recipients: + "A Group:Ed Jones <c@a.invalid>,joe@where.invalid,John <jdoe@one.invalid>;", + emailAddressOnly: false, + count: 3, + result: [ + "Ed Jones <c@a.invalid>", + "joe@where.invalid", + "John <jdoe@one.invalid>", + ], + }, + { + recipients: + "mygroup:;, empty:;, foo@foo.invalid, othergroup:bar@foo.invalid, bar2@foo.invalid;, y@y.invalid, empty:;", + emailAddressOnly: true, + count: 4, + result: [ + "foo@foo.invalid", + "bar@foo.invalid", + "bar2@foo.invalid", + "y@y.invalid", + ], + }, + { + recipients: "Undisclosed recipients:;;;;;;;;;;;;;;;;,,,,,,,,,,,,,,,,", + emailAddressOnly: true, + count: 0, + result: [], + }, + { + recipients: "a@xxx.invalid; b@xxx.invalid", + emailAddressOnly: true, + count: 2, + result: ["a@xxx.invalid", "b@xxx.invalid"], + }, + { + recipients: "a@xxx.invalid; B <b@xxx.invalid>", + emailAddressOnly: false, + count: 2, + result: ["a@xxx.invalid", "B <b@xxx.invalid>"], + }, + { + recipients: '"A " <a@xxx.invalid>; b@xxx.invalid', + emailAddressOnly: false, + count: 2, + result: ["A <a@xxx.invalid>", "b@xxx.invalid"], + }, + { + recipients: "A <a@xxx.invalid>; B <b@xxx.invalid>", + emailAddressOnly: false, + count: 2, + result: ["A <a@xxx.invalid>", "B <b@xxx.invalid>"], + }, + { + recipients: + "A (this: is, a comment;) <a.invalid>; g: (this: is, <a> comment;) C <c.invalid>, d.invalid;", + emailAddressOnly: false, + count: 3, + result: [ + "A (this: is, a comment;) <a.invalid>", + "(this: is, <a> comment;) C <c.invalid>", + "d.invalid <>", + ], + }, + { + recipients: + 'Mary Smith <mary@x.invalid>, extra:;, group:jdoe@example.invalid; Who? <one@y.invalid>; <boss@nil.invalid>, "Giant; \\"Big\\" Box" <sysservices@example.invalid>, ', + emailAddressOnly: false, + count: 5, + result: [ + "Mary Smith <mary@x.invalid>", + "jdoe@example.invalid", + "Who? <one@y.invalid>", + "boss@nil.invalid", + 'Giant; "Big" Box <sysservices@example.invalid>', + ], + }, + { + recipients: "Undisclosed recipients: a@foo.invalid ;;extra:;", + emailAddressOnly: true, + count: 1, + result: ["a@foo.invalid"], + }, + { + recipients: "Undisclosed recipients:;;extra:a@foo.invalid;", + emailAddressOnly: true, + count: 1, + result: ["a@foo.invalid"], + }, + { + recipients: "", + emailAddressOnly: false, + count: 0, + result: [], + }, +]; + +function run_test() { + var fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + + // As most of SplitRecipients functionality is in the nsIMsgHeaderParser + // functionality, here (at least initially), we're just interested in checking + // the basic argument/return combinations. + + for (var part = 0; part < splitRecipientsTests.length; ++part) { + print("Test: " + splitRecipientsTests[part].recipients); + var result = fields.splitRecipients( + splitRecipientsTests[part].recipients, + splitRecipientsTests[part].emailAddressOnly + ); + + Assert.equal(splitRecipientsTests[part].count, result.length); + + for (var item = 0; item < result.length; ++item) { + Assert.equal(splitRecipientsTests[part].result[item], result[item]); + } + } +} diff --git a/comm/mailnews/compose/test/unit/test_staleTemporaryFileCleanup.js b/comm/mailnews/compose/test/unit/test_staleTemporaryFileCleanup.js new file mode 100644 index 0000000000..427c101914 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_staleTemporaryFileCleanup.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test that stale temporary files are cleaned up when the msg compose service + * is initialized. + */ + +var gExpectedFiles; + +function create_temporary_files_for(name) { + let file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append(name); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + return file; +} + +function collect_expected_temporary_files() { + let files = []; + + files.push(create_temporary_files_for("nsmail.tmp")); + files.push(create_temporary_files_for("nsmail.tmp")); + files.push(create_temporary_files_for("nsmail.tmp")); + files.push(create_temporary_files_for("nsemail.eml")); + files.push(create_temporary_files_for("nsemail.tmp")); + files.push(create_temporary_files_for("nsqmail.tmp")); + files.push(create_temporary_files_for("nscopy.tmp")); + files.push(create_temporary_files_for("nscopy.tmp")); + + return files; +} + +function check_files_not_exist(files) { + files.forEach(function (file) { + Assert.ok(!file.exists()); + }); +} + +function run_test() { + gExpectedFiles = collect_expected_temporary_files(); + registerCleanupFunction(function () { + gExpectedFiles.forEach(function (file) { + if (file.exists()) { + file.remove(false); + } + }); + }); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + MailServices.compose; // Initialise the compose service. + do_test_pending(); + check_files_not_exist(gExpectedFiles); + do_test_finished(); +} diff --git a/comm/mailnews/compose/test/unit/test_telemetry_compose.js b/comm/mailnews/compose/test/unit/test_telemetry_compose.js new file mode 100644 index 0000000000..f48db07293 --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_telemetry_compose.js @@ -0,0 +1,109 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test telemetry related to message composition. + */ + +ChromeUtils.defineESModuleGetters(this, { + TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs", +}); + +const HTML_SCALAR = "tb.compose.format_html"; +const PLAIN_TEXT_SCALAR = "tb.compose.format_plain_text"; + +/** + * Check that we're counting HTML or Plain text when composing. + */ +add_task(async function test_compose_format() { + Services.telemetry.clearScalars(); + + // Bare-bones code to initiate composing a message in given format. + let createCompose = function (fmt) { + let msgCompose = Cc[ + "@mozilla.org/messengercompose/compose;1" + ].createInstance(Ci.nsIMsgCompose); + + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + + params.format = fmt; + msgCompose.initialize(params); + }; + + // Start composing arbitrary numbers of messages in each format. + const NUM_HTML = 7; + const NUM_PLAIN = 13; + for (let i = 0; i < NUM_HTML; i++) { + createCompose(Ci.nsIMsgCompFormat.HTML); + } + for (let i = 0; i < NUM_PLAIN; i++) { + createCompose(Ci.nsIMsgCompFormat.PlainText); + } + + // Did we count them correctly? + const scalars = TelemetryTestUtils.getProcessScalars("parent"); + Assert.equal( + scalars[HTML_SCALAR], + NUM_HTML, + HTML_SCALAR + " must have the correct value." + ); + Assert.equal( + scalars[PLAIN_TEXT_SCALAR], + NUM_PLAIN, + PLAIN_TEXT_SCALAR + " must have the correct value." + ); +}); + +/** + * Check that we're counting compose type (new/reply/fwd etc) when composing. + */ +add_task(async function test_compose_type() { + // Bare-bones code to initiate composing a message in given type. + let createCompose = function (type) { + let msgCompose = Cc[ + "@mozilla.org/messengercompose/compose;1" + ].createInstance(Ci.nsIMsgCompose); + + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + + params.type = type; + msgCompose.initialize(params); + }; + const histogram = TelemetryTestUtils.getAndClearHistogram("TB_COMPOSE_TYPE"); + + // Start composing arbitrary numbers of messages in each format. + const NUM_NEW = 4; + const NUM_DRAFT = 7; + const NUM_EDIT_TEMPLATE = 3; + for (let i = 0; i < NUM_NEW; i++) { + createCompose(Ci.nsIMsgCompType.New); + } + for (let i = 0; i < NUM_DRAFT; i++) { + createCompose(Ci.nsIMsgCompType.Draft); + } + for (let i = 0; i < NUM_EDIT_TEMPLATE; i++) { + createCompose(Ci.nsIMsgCompType.EditTemplate); + } + + // Did we count them correctly? + const snapshot = histogram.snapshot(); + Assert.equal( + snapshot.values[Ci.nsIMsgCompType.New], + NUM_NEW, + "nsIMsgCompType.New count must be correct" + ); + Assert.equal( + snapshot.values[Ci.nsIMsgCompType.Draft], + NUM_DRAFT, + "nsIMsgCompType.Draft count must be correct" + ); + Assert.equal( + snapshot.values[Ci.nsIMsgCompType.EditTemplate], + NUM_EDIT_TEMPLATE, + "nsIMsgCompType.EditTemplate count must be correct" + ); +}); diff --git a/comm/mailnews/compose/test/unit/test_temporaryFilesRemoved.js b/comm/mailnews/compose/test/unit/test_temporaryFilesRemoved.js new file mode 100644 index 0000000000..6a350ac64e --- /dev/null +++ b/comm/mailnews/compose/test/unit/test_temporaryFilesRemoved.js @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test that temporary files for draft are surely removed. + */ + +var gMsgCompose; +var gExpectedFiles; + +var progressListener = { + onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + do_timeout(0, checkResult); + } + }, + + onProgressChange( + aWebProgress, + aRequest, + aCurSelfProgress, + aMaxSelfProgress, + aCurTotalProgress, + aMaxTotalProgress + ) {}, + onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {}, + onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {}, + onSecurityChange(aWebProgress, aRequest, state) {}, + onContentBlockingEvent(aWebProgress, aRequest, aEvent) {}, + + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), +}; + +/** + * Get the count of temporary files. Because nsIFile.createUnique creates a random + * file name, we iterate the tmp dir and count the files that match filename + * patterns. + */ +async function getTemporaryFilesCount() { + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path; + let entries = await IOUtils.getChildren(tmpDir); + let tempFiles = { + "nsmail.tmp": 0, + "nscopy.tmp": 0, + "nsemail.eml": 0, + "nsemail.tmp": 0, + "nsqmail.tmp": 0, + }; + for (const path of entries) { + for (let pattern of Object.keys(tempFiles)) { + let [name, extName] = pattern.split("."); + if (PathUtils.filename(path).startsWith(name) && path.endsWith(extName)) { + tempFiles[pattern]++; + } + } + } + return tempFiles; +} + +/** + * Temp files should be deleted as soon as the draft is finished saving, so the + * counts should be the same as before. + */ +async function checkResult() { + let filesCount = await getTemporaryFilesCount(); + for (let [pattern, count] of Object.entries(filesCount)) { + Assert.equal( + count, + gExpectedFiles[pattern], + `${pattern} should not exists` + ); + } + do_test_finished(); +} + +add_task(async function () { + gExpectedFiles = await getTemporaryFilesCount(); + + // Ensure we have at least one mail account + localAccountUtils.loadLocalMailAccount(); + + gMsgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance( + Ci.nsIMsgCompose + ); + let fields = Cc[ + "@mozilla.org/messengercompose/composefields;1" + ].createInstance(Ci.nsIMsgCompFields); + let params = Cc[ + "@mozilla.org/messengercompose/composeparams;1" + ].createInstance(Ci.nsIMsgComposeParams); + + fields.from = "Nobody <nobody@tinderbox.test>"; + fields.body = "body text"; + fields.useMultipartAlternative = true; + + params.composeFields = fields; + params.format = Ci.nsIMsgCompFormat.HTML; + + gMsgCompose.initialize(params, null, null); + + let identity = getSmtpIdentity(null, getBasicSmtpServer()); + + localAccountUtils.rootFolder.createLocalSubfolder("Drafts"); + + let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance( + Ci.nsIMsgProgress + ); + progress.registerListener(progressListener); + + do_test_pending(); + + gMsgCompose.sendMsg( + Ci.nsIMsgSend.nsMsgSaveAsDraft, + identity, + "", + null, + progress + ); +}); diff --git a/comm/mailnews/compose/test/unit/xpcshell.ini b/comm/mailnews/compose/test/unit/xpcshell.ini new file mode 100644 index 0000000000..687259c42a --- /dev/null +++ b/comm/mailnews/compose/test/unit/xpcshell.ini @@ -0,0 +1,54 @@ +[DEFAULT] +head = head_compose.js +tail = +support-files = data/* + +[test_accountKey.js] +[test_attachment.js] +[test_attachment_intl.js] +[test_autoReply.js] +skip-if = os == 'mac' +[test_bcc.js] +[test_bug155172.js] +[test_bug474774.js] +[test_createAndSendMessage.js] +[test_createRFC822Message.js] +[test_detectAttachmentCharset.js] +[test_expandMailingLists.js] +[test_fcc2.js] +[test_fccReply.js] +[test_longLines.js] +[test_mailTelemetry.js] +[test_mailtoURL.js] +[test_messageBody.js] +[test_messageHeaders.js] +[test_nsIMsgCompFields.js] +[test_nsMsgCompose1.js] +[test_nsMsgCompose2.js] +[test_nsMsgCompose3.js] +[test_nsSmtpService1.js] +[test_saveDraft.js] +[test_sendBackground.js] +[test_sendMailAddressIDN.js] +[test_sendMailMessage.js] +[test_sendMessageFile.js] +[test_sendMessageLater.js] +[test_sendMessageLater2.js] +[test_sendMessageLater3.js] +[test_sendObserver.js] +[test_smtp8bitMime.js] +[test_smtpAuthMethods.js] +[test_smtpClient.js] +[test_smtpPassword.js] +[test_smtpPassword2.js] +[test_smtpPasswordFailure1.js] +[test_smtpPasswordFailure2.js] +[test_smtpPasswordFailure3.js] +[test_smtpProtocols.js] +[test_smtpProxy.js] +[test_smtpServer.js] +[test_smtpURL.js] +[test_splitRecipients.js] +[test_telemetry_compose.js] +[test_staleTemporaryFileCleanup.js] +[test_temporaryFilesRemoved.js] |