sup 1.0 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/checks.yml +58 -0
- data/.rubocop.yml +5 -0
- data/CONTRIBUTORS +5 -2
- data/Gemfile +5 -1
- data/History.txt +33 -0
- data/Manifest.txt +171 -0
- data/README.md +9 -4
- data/Rakefile +40 -1
- data/bin/sup-add +4 -8
- data/bin/sup-sync-back-maildir +1 -1
- data/contrib/nix/Gemfile +22 -0
- data/contrib/nix/Gemfile.lock +80 -0
- data/contrib/nix/README +7 -0
- data/contrib/nix/gem-install-shell.nix +12 -0
- data/contrib/nix/gemset.nix +339 -0
- data/contrib/nix/ruby2.4-Gemfile.lock +81 -0
- data/contrib/nix/ruby2.4-gemset.nix +309 -0
- data/contrib/nix/ruby2.4-shell.nix +30 -0
- data/contrib/nix/ruby2.5-Gemfile.lock +81 -0
- data/contrib/nix/ruby2.5-gemset.nix +309 -0
- data/contrib/nix/ruby2.5-shell.nix +30 -0
- data/contrib/nix/ruby2.6-Gemfile.lock +83 -0
- data/contrib/nix/ruby2.6-gemset.nix +319 -0
- data/contrib/nix/ruby2.6-shell.nix +30 -0
- data/contrib/nix/ruby2.7-shell.nix +23 -0
- data/contrib/nix/ruby3.0-shell.nix +23 -0
- data/contrib/nix/ruby3.1-shell.nix +23 -0
- data/contrib/nix/ruby3.2-shell.nix +23 -0
- data/contrib/nix/ruby3.3-shell.nix +23 -0
- data/contrib/nix/test-all-rubies.sh +6 -0
- data/doc/Hooks.txt +1 -1
- data/ext/mkrf_conf_xapian.rb +12 -6
- data/lib/sup/colormap.rb +1 -1
- data/lib/sup/crypto.rb +1 -1
- data/lib/sup/hook.rb +1 -1
- data/lib/sup/index.rb +4 -4
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/maildir.rb +5 -5
- data/lib/sup/mbox.rb +5 -5
- data/lib/sup/message.rb +8 -7
- data/lib/sup/message_chunks.rb +27 -19
- data/lib/sup/modes/completion_mode.rb +0 -1
- data/lib/sup/modes/console_mode.rb +1 -1
- data/lib/sup/modes/file_browser_mode.rb +2 -2
- data/lib/sup/modes/label_list_mode.rb +1 -1
- data/lib/sup/modes/search_list_mode.rb +2 -2
- data/lib/sup/modes/thread_view_mode.rb +1 -2
- data/lib/sup/rfc2047.rb +21 -6
- data/lib/sup/source.rb +8 -2
- data/lib/sup/textfield.rb +0 -1
- data/lib/sup/thread.rb +20 -21
- data/lib/sup/util.rb +31 -53
- data/lib/sup/version.rb +1 -1
- data/lib/sup.rb +12 -8
- data/man/sup-add.1 +39 -39
- data/man/sup-config.1 +31 -27
- data/man/sup-dump.1 +34 -35
- data/man/sup-import-dump.1 +36 -32
- data/man/sup-psych-ify-config-files.1 +29 -25
- data/man/sup-recover-sources.1 +32 -28
- data/man/sup-sync-back-maildir.1 +34 -30
- data/man/sup-sync.1 +40 -36
- data/man/sup-tweak-labels.1 +36 -32
- data/man/sup.1 +41 -37
- data/shell.nix +1 -0
- data/sup.gemspec +6 -4
- data/test/dummy_source.rb +21 -15
- data/test/fixtures/embedded-message.eml +34 -0
- data/test/fixtures/non-ascii-header-in-nested-message.eml +36 -0
- data/test/fixtures/non-ascii-header.eml +8 -0
- data/test/fixtures/rfc2047-header-encoding.eml +15 -0
- data/test/fixtures/text-attachments-with-charset.eml +15 -1
- data/test/fixtures/utf8-header.eml +17 -0
- data/test/integration/test_maildir.rb +3 -0
- data/test/integration/test_mbox.rb +4 -1
- data/test/integration/test_sup-add.rb +83 -0
- data/test/integration/test_sup-sync-back-maildir.rb +40 -0
- data/test/test_crypto.rb +44 -0
- data/test/test_header_parsing.rb +11 -3
- data/test/test_helper.rb +7 -4
- data/test/test_message.rb +124 -32
- data/test/test_messages_dir.rb +13 -15
- data/test/unit/test_horizontal_selector.rb +4 -4
- data/test/unit/test_locale_fiddler.rb +1 -1
- data/test/unit/util/test_query.rb +1 -1
- data/test/unit/util/test_string.rb +3 -3
- data/test/unit/util/test_uri.rb +2 -2
- metadata +69 -18
- data/.travis.yml +0 -18
- data/bin/sup-psych-ify-config-files +0 -21
- data/test/integration/test_label_service.rb +0 -18
- data/test/test_yaml_migration.rb +0 -85
@@ -0,0 +1,36 @@
|
|
1
|
+
Return-Path: <spammer@example.com>
|
2
|
+
From: SPAM � <spammer@example.com>
|
3
|
+
To: <recipient@example.invalid>
|
4
|
+
Subject: spam � spam
|
5
|
+
MIME-Version: 1.0
|
6
|
+
Content-Type: multipart/mixed; boundary="----------=_4F506AC2.EE281DC4"
|
7
|
+
Message-Id: <20120302063755.0FE2122017@a.a.a.a>
|
8
|
+
Date: Fri, 2 Mar 2012 07:37:55 +0100 (CET)
|
9
|
+
|
10
|
+
This is a multi-part message in MIME format.
|
11
|
+
|
12
|
+
------------=_4F506AC2.EE281DC4
|
13
|
+
Content-Type: text/plain; charset=iso-8859-1
|
14
|
+
Content-Disposition: inline
|
15
|
+
Content-Transfer-Encoding: 8bit
|
16
|
+
|
17
|
+
Spam detection software, running on the system "a.a.a.a.a.", has
|
18
|
+
identified this incoming email as possible spam. The original message
|
19
|
+
has been attached to this so you can view it (if it isn't spam) or label
|
20
|
+
similar future email.
|
21
|
+
|
22
|
+
|
23
|
+
------------=_4F506AC2.EE281DC4
|
24
|
+
Content-Type: message/rfc822; x-spam-type=original
|
25
|
+
Content-Description: original message before SpamAssassin
|
26
|
+
Content-Disposition: attachment
|
27
|
+
Content-Transfer-Encoding: 8bit
|
28
|
+
|
29
|
+
From: SPAM � <spammer@example.com>
|
30
|
+
To: <enclosed@example.invalid>
|
31
|
+
Subject: spam � spam
|
32
|
+
|
33
|
+
This is a spam.
|
34
|
+
|
35
|
+
------------=_4F506AC2.EE281DC4--
|
36
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
From: test@example.invalid
|
2
|
+
To: test@example.invalid
|
3
|
+
Date: Sun, 19 Jul 2020 17:03:56 +1000
|
4
|
+
Subject:
|
5
|
+
=?US-ASCII?q?Hans Martin Djupvik?= =?ISO-8859-1?q?,_Ingrid_B=F8?=
|
6
|
+
=?KOI8-R?b?LCDp0snOwSDzycTP0s/XwQ?=
|
7
|
+
=?UTF-16?b?//4sACAASgBlAHMAcABlAHIAIABCAGUAcgBnAA?=
|
8
|
+
=?UTF-7?b?LCBGcmlkYSBFbmcrQVBnLQ?=
|
9
|
+
bad: =?UTF16?q?badcharsetname?= =?US-ASCII?b?/w?=
|
10
|
+
=?UTF-7?Q?=41=6D=65=72=69=63=61=E2=80=99=73?=
|
11
|
+
|
12
|
+
The subject header contains various RFC2047 encoded words.
|
13
|
+
For completeness we test both base64 and quoted-printable, and some
|
14
|
+
ASCII-incompatible encodings.
|
15
|
+
We also include some bogus words which cannot be decoded.
|
@@ -42,5 +42,19 @@ Content-Disposition: attachment; filename="bad.txt"
|
|
42
42
|
MIME-Version: 1.0
|
43
43
|
|
44
44
|
Embedded=F0garbage
|
45
|
-
|
45
|
+
|
46
|
+
--===============2385509127900810307==
|
47
|
+
Content-Type: text/plain; charset="invalid-test"; name="invalid-charset.txt"
|
48
|
+
Content-Transfer-Encoding: quoted-printable
|
49
|
+
Content-Disposition: attachment; filename="invalid-charset.txt"
|
50
|
+
|
51
|
+
Example invalid charset
|
52
|
+
--===============2385509127900810307==
|
53
|
+
Content-Type: text/plain; charset="utf-7"
|
54
|
+
Content-Transfer-Encoding: 7bit
|
55
|
+
MIME-Version: 1.0
|
56
|
+
Content-Disposition: attachment; filename="ascii.txt"
|
57
|
+
|
58
|
+
This is +Jyg-UTF-7+Jyg-
|
59
|
+
--===============2385509127900810307==
|
46
60
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Delivered-To: djc@djc.id.au
|
2
|
+
Received: from orpheus.librarything.com (orpheus.librarything.com [74.201.105.9])
|
3
|
+
by djc.id.au (Postfix) with ESMTP id A0CCB20AAB99
|
4
|
+
for <djc@djc.id.au>; Sat, 23 Jan 2021 02:52:15 +1000 (AEST)
|
5
|
+
Received: by orpheus.librarything.com (Postfix, from userid 0)
|
6
|
+
id 21B172C20F9; Fri, 22 Jan 2021 11:52:08 -0500 (EST)
|
7
|
+
To: djc@djc.id.au
|
8
|
+
Subject: LibraryThing: State of the Thing — January
|
9
|
+
MIME-Version: 1.0
|
10
|
+
Content-type: text/html; charset=iso-8859-1
|
11
|
+
From: tim@librarything.com
|
12
|
+
Reply-To: tim@librarything.com
|
13
|
+
Message-Id: <20210122165208.21B172C20F9@orpheus.librarything.com>
|
14
|
+
Date: Fri, 22 Jan 2021 11:52:08 -0500 (EST)
|
15
|
+
Return-Path: <root@orpheus.librarything.com>
|
16
|
+
|
17
|
+
<p>Some stuff</p>
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class TestSupAdd < Minitest::Test
|
2
|
+
|
3
|
+
def setup
|
4
|
+
@path = Dir.mktmpdir
|
5
|
+
end
|
6
|
+
|
7
|
+
def teardown
|
8
|
+
FileUtils.rm_r @path
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_can_add_maildir_source
|
12
|
+
_out, _err = capture_subprocess_io do
|
13
|
+
assert system({"SUP_BASE" => @path}, "bin/sup-add", "maildir:///some/path")
|
14
|
+
end
|
15
|
+
|
16
|
+
generated_sources_yaml = File.read "#{@path}/sources.yaml"
|
17
|
+
assert_equal <<EOS, generated_sources_yaml
|
18
|
+
---
|
19
|
+
- !<tag:supmua.org,2006-10-01/Redwood/Maildir>
|
20
|
+
uri: maildir:///some/path
|
21
|
+
usual: true
|
22
|
+
archived: false
|
23
|
+
sync_back: true
|
24
|
+
id: 1
|
25
|
+
labels: []
|
26
|
+
EOS
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_fixes_old_tag_uri_syntax
|
30
|
+
File.write "#{@path}/sources.yaml", <<EOS
|
31
|
+
---
|
32
|
+
- !supmua.org,2006-10-01/Redwood/Maildir
|
33
|
+
uri: maildir:/some/path
|
34
|
+
usual: true
|
35
|
+
archived: false
|
36
|
+
sync_back: true
|
37
|
+
id: 1
|
38
|
+
labels: []
|
39
|
+
EOS
|
40
|
+
_out, _err = capture_subprocess_io do
|
41
|
+
assert system({"SUP_BASE" => @path}, "bin/sup-add", "maildir:///other/path")
|
42
|
+
end
|
43
|
+
|
44
|
+
generated_sources_yaml = File.read "#{@path}/sources.yaml"
|
45
|
+
assert_equal <<EOS, generated_sources_yaml
|
46
|
+
---
|
47
|
+
- !<tag:supmua.org,2006-10-01/Redwood/Maildir>
|
48
|
+
uri: maildir:/some/path
|
49
|
+
usual: true
|
50
|
+
archived: false
|
51
|
+
sync_back: true
|
52
|
+
id: 1
|
53
|
+
labels: []
|
54
|
+
- !<tag:supmua.org,2006-10-01/Redwood/Maildir>
|
55
|
+
uri: maildir:///other/path
|
56
|
+
usual: true
|
57
|
+
archived: false
|
58
|
+
sync_back: true
|
59
|
+
id: 2
|
60
|
+
labels: []
|
61
|
+
EOS
|
62
|
+
end
|
63
|
+
|
64
|
+
## https://github.com/sup-heliotrope/sup/issues/545
|
65
|
+
def test_source_with_invalid_uri_chars_in_path
|
66
|
+
_out, _err = capture_subprocess_io do
|
67
|
+
assert system({"SUP_BASE" => @path}, "bin/sup-add", "maildir:~/.mail/gmail/[Gmail]/.All Mail/")
|
68
|
+
end
|
69
|
+
|
70
|
+
generated_sources_yaml = File.read "#{@path}/sources.yaml"
|
71
|
+
assert_equal <<EOS, generated_sources_yaml
|
72
|
+
---
|
73
|
+
- !<tag:supmua.org,2006-10-01/Redwood/Maildir>
|
74
|
+
uri: maildir:~/.mail/gmail/[Gmail]/.All Mail/
|
75
|
+
usual: true
|
76
|
+
archived: false
|
77
|
+
sync_back: true
|
78
|
+
id: 1
|
79
|
+
labels: []
|
80
|
+
EOS
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestSupSyncBackMaildir < Minitest::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@path = Dir.mktmpdir
|
7
|
+
|
8
|
+
@maildir = File.join @path, "test_maildir"
|
9
|
+
Dir.mkdir @maildir
|
10
|
+
%w[cur new tmp].each do |subdir|
|
11
|
+
Dir.mkdir (File.join @maildir, subdir)
|
12
|
+
end
|
13
|
+
msg_path = File.join @maildir, "new", "123.hostname"
|
14
|
+
FileUtils.copy_file fixture_path("simple-message.eml"), msg_path
|
15
|
+
|
16
|
+
_out, _err = capture_subprocess_io do
|
17
|
+
assert system({"SUP_BASE" => @path}, "bin/sup-add", "maildir://#{@maildir}")
|
18
|
+
assert system({"SUP_BASE" => @path}, "bin/sup-sync")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
FileUtils.rm_r @path
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_it_syncs_seen_unread_flags
|
27
|
+
_out, _err = capture_subprocess_io do
|
28
|
+
assert system({"SUP_BASE" => @path},
|
29
|
+
"bin/sup-tweak-labels",
|
30
|
+
"--all-sources",
|
31
|
+
"--add=replied",
|
32
|
+
"--remove=unread")
|
33
|
+
assert system({"SUP_BASE" => @path}, "bin/sup-sync-back-maildir", "--no-confirm")
|
34
|
+
end
|
35
|
+
|
36
|
+
refute File.exist? (File.join @maildir, "new", "123.hostname")
|
37
|
+
assert File.exist? (File.join @maildir, "cur", "123.hostname:2,RS")
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/test/test_crypto.rb
CHANGED
@@ -69,6 +69,24 @@ class TestCryptoManager < Minitest::Test
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
+
def test_sign_nested_parts
|
73
|
+
if CryptoManager.have_crypto? then
|
74
|
+
body = RMail::Message.new
|
75
|
+
body.header["Content-Disposition"] = "inline"
|
76
|
+
body.body = "ABCDEFG"
|
77
|
+
payload = RMail::Message.new
|
78
|
+
payload.header["MIME-Version"] = "1.0"
|
79
|
+
payload.add_part body
|
80
|
+
payload.add_part RMail::Message.make_attachment "attachment", "text/plain", nil, "attachment.txt"
|
81
|
+
signed = CryptoManager.sign @from_email, @to_email, payload
|
82
|
+
## The result is a multipart/signed containing a multipart/mixed.
|
83
|
+
## There should be a MIME-Version header on the top-level
|
84
|
+
## multipart/signed message, but *not* on the enclosed
|
85
|
+
## multipart/mixed part.
|
86
|
+
assert_equal 1, signed.to_s.scan(/MIME-Version:/).size
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
72
90
|
def test_encrypt
|
73
91
|
if CryptoManager.have_crypto? then
|
74
92
|
encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
|
@@ -116,6 +134,32 @@ class TestCryptoManager < Minitest::Test
|
|
116
134
|
CryptoManager.verify signed.body[0], signed.body[1], true
|
117
135
|
end
|
118
136
|
end
|
137
|
+
|
138
|
+
def test_verify_nested_parts
|
139
|
+
if CryptoManager.have_crypto?
|
140
|
+
## Generate a multipart/signed containing a multipart/mixed.
|
141
|
+
## We will test verifying the generated signature below.
|
142
|
+
## Importantly, the inner multipart/mixed does *not* have a
|
143
|
+
## MIME-Version header because it is not a top-level message.
|
144
|
+
payload = RMail::Parser.read <<EOS
|
145
|
+
Content-Type: multipart/mixed; boundary="=-1652088224-7794-561531-1825-1-="
|
146
|
+
|
147
|
+
|
148
|
+
--=-1652088224-7794-561531-1825-1-=
|
149
|
+
Content-Disposition: inline
|
150
|
+
|
151
|
+
ABCDEFG
|
152
|
+
--=-1652088224-7794-561531-1825-1-=
|
153
|
+
Content-Disposition: attachment; filename="attachment.txt"
|
154
|
+
Content-Type: text/plain; name="attachment.txt"
|
155
|
+
|
156
|
+
attachment
|
157
|
+
--=-1652088224-7794-561531-1825-1-=--
|
158
|
+
EOS
|
159
|
+
signed = CryptoManager.sign @from_email_ecc, @to_email, payload
|
160
|
+
CryptoManager.verify payload, signed.body[1], true
|
161
|
+
end
|
162
|
+
end
|
119
163
|
end
|
120
164
|
|
121
165
|
end
|
data/test/test_header_parsing.rb
CHANGED
@@ -4,16 +4,22 @@ require 'test_helper'
|
|
4
4
|
require 'sup'
|
5
5
|
require 'stringio'
|
6
6
|
|
7
|
-
include Redwood
|
8
|
-
|
9
7
|
class TestMBoxParsing < Minitest::Test
|
10
8
|
|
9
|
+
include Redwood
|
10
|
+
|
11
11
|
def setup
|
12
12
|
@path = Dir.mktmpdir
|
13
13
|
@mbox = File.join(@path, 'test_mbox')
|
14
|
+
@log = StringIO.new
|
15
|
+
Redwood::Logger.add_sink @log
|
16
|
+
Redwood::Logger.remove_sink $stderr
|
14
17
|
end
|
15
18
|
|
16
19
|
def teardown
|
20
|
+
Redwood::Logger.clear!
|
21
|
+
Redwood::Logger.remove_sink @log
|
22
|
+
Redwood::Logger.add_sink $stderr
|
17
23
|
FileUtils.rm_r @path
|
18
24
|
end
|
19
25
|
|
@@ -69,7 +75,7 @@ EOS
|
|
69
75
|
|
70
76
|
def test_blank_lines
|
71
77
|
h = Source.parse_raw_email_header StringIO.new("")
|
72
|
-
|
78
|
+
assert_nil h["message-id"]
|
73
79
|
end
|
74
80
|
|
75
81
|
def test_empty_headers
|
@@ -133,6 +139,8 @@ EOS
|
|
133
139
|
assert_equal 61, offset
|
134
140
|
offset = l.next_offset 61
|
135
141
|
assert_nil offset
|
142
|
+
assert_match(/WARNING: found invalid date in potential mbox split line, not splitting/,
|
143
|
+
@log.string)
|
136
144
|
end
|
137
145
|
|
138
146
|
def test_more_from_line_splitting
|
data/test/test_helper.rb
CHANGED
@@ -2,9 +2,12 @@ require "rubygems" rescue nil
|
|
2
2
|
require 'minitest/autorun'
|
3
3
|
require "rr"
|
4
4
|
|
5
|
-
def
|
5
|
+
def fixture_path(filename)
|
6
|
+
File.expand_path("../fixtures/#{filename}", __FILE__)
|
7
|
+
end
|
8
|
+
|
9
|
+
def fixture_contents(filename)
|
6
10
|
file = ''
|
7
|
-
|
8
|
-
File.open(path) { |io| file = io.read }
|
11
|
+
File.open(fixture_path(filename)) { |io| file = io.read }
|
9
12
|
file
|
10
|
-
end
|
13
|
+
end
|
data/test/test_message.rb
CHANGED
@@ -21,10 +21,8 @@ class TestMessage < Minitest::Test
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_simple_message
|
24
|
-
message = fixture('simple-message.eml')
|
25
|
-
|
26
24
|
source = DummySource.new("sup-test://test_simple_message")
|
27
|
-
source.messages = [ message ]
|
25
|
+
source.messages = [ fixture_path('simple-message.eml') ]
|
28
26
|
source_info = 0
|
29
27
|
|
30
28
|
sup_message = Message.build_from_source(source, source_info)
|
@@ -99,10 +97,8 @@ class TestMessage < Minitest::Test
|
|
99
97
|
end
|
100
98
|
|
101
99
|
def test_multipart_message
|
102
|
-
message = fixture('multi-part.eml')
|
103
|
-
|
104
100
|
source = DummySource.new("sup-test://test_multipart_message")
|
105
|
-
source.messages = [
|
101
|
+
source.messages = [ fixture_path('multi-part.eml') ]
|
106
102
|
source_info = 0
|
107
103
|
|
108
104
|
sup_message = Message.build_from_source(source, source_info)
|
@@ -126,10 +122,8 @@ class TestMessage < Minitest::Test
|
|
126
122
|
end
|
127
123
|
|
128
124
|
def test_broken_message_1
|
129
|
-
message = fixture('missing-from-to.eml')
|
130
|
-
|
131
125
|
source = DummySource.new("sup-test://test_broken_message_1")
|
132
|
-
source.messages = [
|
126
|
+
source.messages = [ fixture_path('missing-from-to.eml') ]
|
133
127
|
source_info = 0
|
134
128
|
|
135
129
|
sup_message = Message.build_from_source(source, source_info)
|
@@ -150,10 +144,8 @@ class TestMessage < Minitest::Test
|
|
150
144
|
end
|
151
145
|
|
152
146
|
def test_broken_message_2
|
153
|
-
message = fixture('no-body.eml')
|
154
|
-
|
155
147
|
source = DummySource.new("sup-test://test_broken_message_1")
|
156
|
-
source.messages = [
|
148
|
+
source.messages = [ fixture_path('no-body.eml') ]
|
157
149
|
source_info = 0
|
158
150
|
|
159
151
|
sup_message = Message.build_from_source(source, source_info)
|
@@ -167,10 +159,8 @@ class TestMessage < Minitest::Test
|
|
167
159
|
end
|
168
160
|
|
169
161
|
def test_multipart_message_2
|
170
|
-
message = fixture('multi-part-2.eml')
|
171
|
-
|
172
162
|
source = DummySource.new("sup-test://test_multipart_message_2")
|
173
|
-
source.messages = [
|
163
|
+
source.messages = [ fixture_path('multi-part-2.eml') ]
|
174
164
|
source_info = 0
|
175
165
|
|
176
166
|
sup_message = Message.build_from_source(source, source_info)
|
@@ -178,21 +168,20 @@ class TestMessage < Minitest::Test
|
|
178
168
|
|
179
169
|
chunks = sup_message.load_from_source! # read the message body chunks
|
180
170
|
|
181
|
-
|
171
|
+
assert_equal(1, chunks.length)
|
172
|
+
assert(chunks[0].is_a? Redwood::Chunk::Attachment)
|
182
173
|
end
|
183
174
|
|
184
175
|
def test_text_attachment_decoding
|
185
|
-
message = fixture('text-attachments-with-charset.eml')
|
186
|
-
|
187
176
|
source = DummySource.new("sup-test://test_text_attachment_decoding")
|
188
|
-
source.messages = [
|
177
|
+
source.messages = [ fixture_path('text-attachments-with-charset.eml') ]
|
189
178
|
source_info = 0
|
190
179
|
|
191
180
|
sup_message = Message.build_from_source(source, source_info)
|
192
181
|
sup_message.load_from_source!
|
193
182
|
|
194
183
|
chunks = sup_message.load_from_source!
|
195
|
-
assert_equal(
|
184
|
+
assert_equal(7, chunks.length)
|
196
185
|
assert(chunks[0].is_a? Redwood::Chunk::Text)
|
197
186
|
## The first attachment declares charset=us-ascii
|
198
187
|
assert(chunks[1].is_a? Redwood::Chunk::Attachment)
|
@@ -207,13 +196,18 @@ class TestMessage < Minitest::Test
|
|
207
196
|
## which will be replaced with U+FFFD REPLACEMENT CHARACTER
|
208
197
|
assert(chunks[4].is_a? Redwood::Chunk::Attachment)
|
209
198
|
assert_equal(["Embedded\ufffdgarbage"], chunks[4].lines)
|
199
|
+
## The fifth attachment has an invalid charset, which should still
|
200
|
+
## be handled gracefully
|
201
|
+
assert(chunks[5].is_a? Redwood::Chunk::Attachment)
|
202
|
+
assert_equal(["Example invalid charset"], chunks[5].lines)
|
203
|
+
## The sixth attachment is UTF-7 encoded
|
204
|
+
assert(chunks[6].is_a? Redwood::Chunk::Attachment)
|
205
|
+
assert_equal(["This is ✨UTF-7✨"], chunks[6].lines)
|
210
206
|
end
|
211
207
|
|
212
208
|
def test_mailing_list_header
|
213
|
-
message = fixture('mailing-list-header.eml')
|
214
|
-
|
215
209
|
source = DummySource.new("sup-test://test_mailing_list_header")
|
216
|
-
source.messages = [
|
210
|
+
source.messages = [ fixture_path('mailing-list-header.eml') ]
|
217
211
|
source_info = 0
|
218
212
|
|
219
213
|
sup_message = Message.build_from_source(source, source_info)
|
@@ -227,10 +221,8 @@ class TestMessage < Minitest::Test
|
|
227
221
|
end
|
228
222
|
|
229
223
|
def test_blank_header_lines
|
230
|
-
message = fixture('blank-header-fields.eml')
|
231
|
-
|
232
224
|
source = DummySource.new("sup-test://test_blank_header_lines")
|
233
|
-
source.messages = [
|
225
|
+
source.messages = [ fixture_path('blank-header-fields.eml') ]
|
234
226
|
source_info = 0
|
235
227
|
|
236
228
|
sup_message = Message.build_from_source(source, source_info)
|
@@ -248,11 +240,113 @@ class TestMessage < Minitest::Test
|
|
248
240
|
|
249
241
|
end
|
250
242
|
|
251
|
-
def
|
252
|
-
|
243
|
+
def test_rfc2047_header_encoding
|
244
|
+
source = DummySource.new("sup-test://test_rfc2047_header_encoding")
|
245
|
+
source.messages = [ fixture_path("rfc2047-header-encoding.eml") ]
|
246
|
+
source_info = 0
|
247
|
+
|
248
|
+
sup_message = Message.build_from_source(source, source_info)
|
249
|
+
sup_message.load_from_source!
|
250
|
+
|
251
|
+
assert_equal("Hans Martin Djupvik, Ingrid Bø, Ирина Сидорова, " +
|
252
|
+
"Jesper Berg, Frida Engø " +
|
253
|
+
"bad: =?UTF16?q?badcharsetname?==?US-ASCII?b?/w?=" +
|
254
|
+
"=?UTF-7?Q?=41=6D=65=72=69=63=61=E2=80=99=73?=",
|
255
|
+
sup_message.subj)
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_nonascii_header
|
259
|
+
## Spammers sometimes send invalid high bytes in the headers.
|
260
|
+
## They will be replaced with U+FFFD REPLACEMENT CHARACTER.
|
261
|
+
source = DummySource.new("sup-test://test_nonascii_header")
|
262
|
+
source.messages = [ fixture_path("non-ascii-header.eml") ]
|
263
|
+
source_info = 0
|
264
|
+
|
265
|
+
sup_message = Message.build_from_source(source, source_info)
|
266
|
+
sup_message.load_from_source!
|
267
|
+
|
268
|
+
assert_equal("SPAM \ufffd", sup_message.from.name)
|
269
|
+
assert_equal("spammer@example.com", sup_message.from.email)
|
270
|
+
assert_equal("spam \ufffd spam", sup_message.subj)
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_utf8_header
|
274
|
+
## UTF-8 is allowed in header values according to RFC6532.
|
275
|
+
source = DummySource.new("sup-test://test_utf8_header")
|
276
|
+
source.messages = [ fixture_path("utf8-header.eml") ]
|
277
|
+
source_info = 0
|
278
|
+
|
279
|
+
sup_message = Message.build_from_source(source, source_info)
|
280
|
+
sup_message.load_from_source!
|
281
|
+
|
282
|
+
assert_equal(Encoding::UTF_8, sup_message.subj.encoding)
|
283
|
+
assert_equal("LibraryThing: State of the Thing — January", sup_message.subj)
|
284
|
+
end
|
285
|
+
|
286
|
+
def test_nonascii_header_in_nested_message
|
287
|
+
source = DummySource.new("sup-test://test_nonascii_header_in_nested_message")
|
288
|
+
source.messages = [ fixture_path("non-ascii-header-in-nested-message.eml") ]
|
289
|
+
source_info = 0
|
290
|
+
|
291
|
+
sup_message = Message.build_from_source(source, source_info)
|
292
|
+
chunks = sup_message.load_from_source!
|
293
|
+
|
294
|
+
assert_equal(3, chunks.length)
|
253
295
|
|
296
|
+
assert(chunks[0].is_a? Redwood::Chunk::Text)
|
297
|
+
|
298
|
+
assert(chunks[1].is_a? Redwood::Chunk::EnclosedMessage)
|
299
|
+
assert_equal(4, chunks[1].lines.length)
|
300
|
+
assert_equal("From: SPAM \ufffd <spammer@example.com>", chunks[1].lines[0])
|
301
|
+
assert_equal("To: enclosed <enclosed@example.invalid>", chunks[1].lines[1])
|
302
|
+
assert_equal("Subject: spam \ufffd spam", chunks[1].lines[3])
|
303
|
+
|
304
|
+
assert(chunks[2].is_a? Redwood::Chunk::Text)
|
305
|
+
assert_equal(1, chunks[2].lines.length)
|
306
|
+
assert_equal("This is a spam.", chunks[2].lines[0])
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_embedded_message
|
310
|
+
source = DummySource.new("sup-test://test_embedded_message")
|
311
|
+
source.messages = [ fixture_path("embedded-message.eml") ]
|
312
|
+
source_info = 0
|
313
|
+
|
314
|
+
sup_message = Message.build_from_source(source, source_info)
|
315
|
+
|
316
|
+
chunks = sup_message.load_from_source!
|
317
|
+
assert_equal(3, chunks.length)
|
318
|
+
|
319
|
+
assert_equal("sender@example.com", sup_message.from.email)
|
320
|
+
assert_equal("Sender", sup_message.from.name)
|
321
|
+
assert_equal(1, sup_message.to.length)
|
322
|
+
assert_equal("recipient@example.invalid", sup_message.to[0].email)
|
323
|
+
assert_equal("recipient", sup_message.to[0].name)
|
324
|
+
assert_equal("Email with embedded message", sup_message.subj)
|
325
|
+
|
326
|
+
assert(chunks[0].is_a? Redwood::Chunk::Text)
|
327
|
+
assert_equal("Example outer message.", chunks[0].lines[0])
|
328
|
+
assert_equal("Example second line.", chunks[0].lines[1])
|
329
|
+
|
330
|
+
assert(chunks[1].is_a? Redwood::Chunk::EnclosedMessage)
|
331
|
+
assert_equal(4, chunks[1].lines.length)
|
332
|
+
assert_equal("From: Embed sender <embed@example.com>", chunks[1].lines[0])
|
333
|
+
assert_equal("To: rcpt2 <rcpt2@example.invalid>", chunks[1].lines[1])
|
334
|
+
assert_equal("Date: ", chunks[1].lines[2][0..5])
|
335
|
+
assert_equal(
|
336
|
+
Time.rfc2822("Wed, 15 Jul 2020 12:34:56 +0000"),
|
337
|
+
Time.rfc2822(chunks[1].lines[2][6..-1])
|
338
|
+
)
|
339
|
+
assert_equal("Subject: Embedded subject line", chunks[1].lines[3])
|
340
|
+
|
341
|
+
assert(chunks[2].is_a? Redwood::Chunk::Text)
|
342
|
+
assert_equal(2, chunks[2].lines.length)
|
343
|
+
assert_equal("Example embedded message.", chunks[2].lines[0])
|
344
|
+
assert_equal("Second line.", chunks[2].lines[1])
|
345
|
+
end
|
346
|
+
|
347
|
+
def test_malicious_attachment_names
|
254
348
|
source = DummySource.new("sup-test://test_blank_header_lines")
|
255
|
-
source.messages = [
|
349
|
+
source.messages = [ fixture_path('malicious-attachment-names.eml') ]
|
256
350
|
source_info = 0
|
257
351
|
|
258
352
|
sup_message = Message.build_from_source(source, source_info)
|
@@ -276,10 +370,8 @@ class TestMessage < Minitest::Test
|
|
276
370
|
# tries to do the right thing and reply after the quote.
|
277
371
|
# In this case we want to just look at the > markers when determining where
|
278
372
|
# the quoted chunk ends.
|
279
|
-
message = fixture('zimbra-quote-with-bottom-post.eml')
|
280
|
-
|
281
373
|
source = DummySource.new("sup-test://test_zimbra_quote_with_bottom_post")
|
282
|
-
source.messages = [
|
374
|
+
source.messages = [ fixture_path('zimbra-quote-with-bottom-post.eml') ]
|
283
375
|
source_info = 0
|
284
376
|
|
285
377
|
sup_message = Message.build_from_source(source, source_info)
|