sup 1.0 → 1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|