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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/checks.yml +58 -0
  3. data/.rubocop.yml +5 -0
  4. data/CONTRIBUTORS +5 -2
  5. data/Gemfile +5 -1
  6. data/History.txt +33 -0
  7. data/Manifest.txt +171 -0
  8. data/README.md +9 -4
  9. data/Rakefile +40 -1
  10. data/bin/sup-add +4 -8
  11. data/bin/sup-sync-back-maildir +1 -1
  12. data/contrib/nix/Gemfile +22 -0
  13. data/contrib/nix/Gemfile.lock +80 -0
  14. data/contrib/nix/README +7 -0
  15. data/contrib/nix/gem-install-shell.nix +12 -0
  16. data/contrib/nix/gemset.nix +339 -0
  17. data/contrib/nix/ruby2.4-Gemfile.lock +81 -0
  18. data/contrib/nix/ruby2.4-gemset.nix +309 -0
  19. data/contrib/nix/ruby2.4-shell.nix +30 -0
  20. data/contrib/nix/ruby2.5-Gemfile.lock +81 -0
  21. data/contrib/nix/ruby2.5-gemset.nix +309 -0
  22. data/contrib/nix/ruby2.5-shell.nix +30 -0
  23. data/contrib/nix/ruby2.6-Gemfile.lock +83 -0
  24. data/contrib/nix/ruby2.6-gemset.nix +319 -0
  25. data/contrib/nix/ruby2.6-shell.nix +30 -0
  26. data/contrib/nix/ruby2.7-shell.nix +23 -0
  27. data/contrib/nix/ruby3.0-shell.nix +23 -0
  28. data/contrib/nix/ruby3.1-shell.nix +23 -0
  29. data/contrib/nix/ruby3.2-shell.nix +23 -0
  30. data/contrib/nix/ruby3.3-shell.nix +23 -0
  31. data/contrib/nix/test-all-rubies.sh +6 -0
  32. data/doc/Hooks.txt +1 -1
  33. data/ext/mkrf_conf_xapian.rb +12 -6
  34. data/lib/sup/colormap.rb +1 -1
  35. data/lib/sup/crypto.rb +1 -1
  36. data/lib/sup/hook.rb +1 -1
  37. data/lib/sup/index.rb +4 -4
  38. data/lib/sup/keymap.rb +1 -1
  39. data/lib/sup/maildir.rb +5 -5
  40. data/lib/sup/mbox.rb +5 -5
  41. data/lib/sup/message.rb +8 -7
  42. data/lib/sup/message_chunks.rb +27 -19
  43. data/lib/sup/modes/completion_mode.rb +0 -1
  44. data/lib/sup/modes/console_mode.rb +1 -1
  45. data/lib/sup/modes/file_browser_mode.rb +2 -2
  46. data/lib/sup/modes/label_list_mode.rb +1 -1
  47. data/lib/sup/modes/search_list_mode.rb +2 -2
  48. data/lib/sup/modes/thread_view_mode.rb +1 -2
  49. data/lib/sup/rfc2047.rb +21 -6
  50. data/lib/sup/source.rb +8 -2
  51. data/lib/sup/textfield.rb +0 -1
  52. data/lib/sup/thread.rb +20 -21
  53. data/lib/sup/util.rb +31 -53
  54. data/lib/sup/version.rb +1 -1
  55. data/lib/sup.rb +12 -8
  56. data/man/sup-add.1 +39 -39
  57. data/man/sup-config.1 +31 -27
  58. data/man/sup-dump.1 +34 -35
  59. data/man/sup-import-dump.1 +36 -32
  60. data/man/sup-psych-ify-config-files.1 +29 -25
  61. data/man/sup-recover-sources.1 +32 -28
  62. data/man/sup-sync-back-maildir.1 +34 -30
  63. data/man/sup-sync.1 +40 -36
  64. data/man/sup-tweak-labels.1 +36 -32
  65. data/man/sup.1 +41 -37
  66. data/shell.nix +1 -0
  67. data/sup.gemspec +6 -4
  68. data/test/dummy_source.rb +21 -15
  69. data/test/fixtures/embedded-message.eml +34 -0
  70. data/test/fixtures/non-ascii-header-in-nested-message.eml +36 -0
  71. data/test/fixtures/non-ascii-header.eml +8 -0
  72. data/test/fixtures/rfc2047-header-encoding.eml +15 -0
  73. data/test/fixtures/text-attachments-with-charset.eml +15 -1
  74. data/test/fixtures/utf8-header.eml +17 -0
  75. data/test/integration/test_maildir.rb +3 -0
  76. data/test/integration/test_mbox.rb +4 -1
  77. data/test/integration/test_sup-add.rb +83 -0
  78. data/test/integration/test_sup-sync-back-maildir.rb +40 -0
  79. data/test/test_crypto.rb +44 -0
  80. data/test/test_header_parsing.rb +11 -3
  81. data/test/test_helper.rb +7 -4
  82. data/test/test_message.rb +124 -32
  83. data/test/test_messages_dir.rb +13 -15
  84. data/test/unit/test_horizontal_selector.rb +4 -4
  85. data/test/unit/test_locale_fiddler.rb +1 -1
  86. data/test/unit/util/test_query.rb +1 -1
  87. data/test/unit/util/test_string.rb +3 -3
  88. data/test/unit/util/test_uri.rb +2 -2
  89. metadata +69 -18
  90. data/.travis.yml +0 -18
  91. data/bin/sup-psych-ify-config-files +0 -21
  92. data/test/integration/test_label_service.rb +0 -18
  93. 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,8 @@
1
+ Return-Path: <spammer@example.com>
2
+ From: SPAM � <spammer@example.com>
3
+ To: <a@b.c>
4
+ Subject: spam � spam
5
+ Message-Id: <20120302063755.0FE2122017@a.a.a.a>
6
+ Date: Fri, 2 Mar 2012 07:37:55 +0100 (CET)
7
+
8
+ https://github.com/sup-heliotrope/sup/issues/205
@@ -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
- --===============2385509127900810307==--
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>
@@ -1,7 +1,10 @@
1
+ require "sup"
1
2
  require "test_helper"
2
3
 
3
4
  class TestMaildir < Minitest::Test
4
5
 
6
+ include Redwood
7
+
5
8
  def setup
6
9
  @path = Dir.mktmpdir
7
10
 
@@ -1,6 +1,9 @@
1
+ require "sup"
1
2
  require "test_helper"
2
3
 
3
- class TestMbox < MiniTest::Test
4
+ class TestMbox < Minitest::Test
5
+
6
+ include Redwood
4
7
 
5
8
  def setup
6
9
  @path = Dir.mktmpdir
@@ -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
@@ -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
- assert_equal nil, h["message-id"]
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 fixture(filename)
5
+ def fixture_path(filename)
6
+ File.expand_path("../fixtures/#{filename}", __FILE__)
7
+ end
8
+
9
+ def fixture_contents(filename)
6
10
  file = ''
7
- path = File.expand_path("../fixtures/#{filename}", __FILE__)
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 = [ message ]
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 = [ message ]
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 = [ message ]
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 = [ message ]
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
- # TODO: Add more asserts
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 = [ message ]
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(5, chunks.length)
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 = [ message ]
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 = [ message ]
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 test_malicious_attachment_names
252
- message = fixture('malicious-attachment-names.eml')
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 = [ message ]
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 = [ message ]
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)