sup 1.1 → 1.3

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/checks.yml +40 -38
  3. data/.gitmodules +1 -0
  4. data/CONTRIBUTORS +3 -1
  5. data/History.txt +32 -3
  6. data/Manifest.txt +34 -1
  7. data/README.md +0 -1
  8. data/bin/sup-sync-back-maildir +1 -1
  9. data/contrib/nix/Gemfile +24 -0
  10. data/contrib/nix/Gemfile.lock +101 -0
  11. data/contrib/nix/README +7 -0
  12. data/contrib/nix/gem-install-shell.nix +14 -0
  13. data/contrib/nix/gemset.nix +391 -0
  14. data/contrib/nix/ruby2.4-Gemfile.lock +85 -0
  15. data/contrib/nix/ruby2.4-gemset.nix +329 -0
  16. data/contrib/nix/ruby2.4-shell.nix +26 -0
  17. data/contrib/nix/ruby2.5-Gemfile.lock +85 -0
  18. data/contrib/nix/ruby2.5-gemset.nix +329 -0
  19. data/contrib/nix/ruby2.5-shell.nix +26 -0
  20. data/contrib/nix/ruby2.6-Gemfile.lock +87 -0
  21. data/contrib/nix/ruby2.6-gemset.nix +339 -0
  22. data/contrib/nix/ruby2.6-shell.nix +26 -0
  23. data/contrib/nix/ruby2.7-Gemfile.lock +91 -0
  24. data/contrib/nix/ruby2.7-gemset.nix +359 -0
  25. data/contrib/nix/ruby2.7-shell.nix +14 -0
  26. data/contrib/nix/ruby3.0-Gemfile.lock +91 -0
  27. data/contrib/nix/ruby3.0-gemset.nix +359 -0
  28. data/contrib/nix/ruby3.0-shell.nix +14 -0
  29. data/contrib/nix/ruby3.1-shell.nix +25 -0
  30. data/contrib/nix/ruby3.2-shell.nix +25 -0
  31. data/contrib/nix/ruby3.3-shell.nix +25 -0
  32. data/contrib/nix/ruby3.4-shell.nix +36 -0
  33. data/contrib/nix/test-all-rubies.sh +6 -0
  34. data/doc/Hooks.txt +1 -1
  35. data/ext/mkrf_conf_xapian.rb +2 -2
  36. data/lib/sup/crypto.rb +8 -6
  37. data/lib/sup/index.rb +2 -2
  38. data/lib/sup/maildir.rb +5 -1
  39. data/lib/sup/mbox.rb +26 -8
  40. data/lib/sup/message.rb +15 -12
  41. data/lib/sup/modes/console_mode.rb +1 -1
  42. data/lib/sup/modes/edit_message_mode.rb +5 -5
  43. data/lib/sup/thread.rb +20 -20
  44. data/lib/sup/util.rb +12 -7
  45. data/lib/sup/version.rb +1 -1
  46. data/man/sup-add.1 +40 -40
  47. data/man/sup-config.1 +30 -26
  48. data/man/sup-dump.1 +38 -38
  49. data/man/sup-import-dump.1 +41 -37
  50. data/man/sup-psych-ify-config-files.1 +32 -28
  51. data/man/sup-recover-sources.1 +39 -35
  52. data/man/sup-sync-back-maildir.1 +39 -34
  53. data/man/sup-sync.1 +50 -46
  54. data/man/sup-tweak-labels.1 +43 -38
  55. data/man/sup.1 +42 -38
  56. data/shell.nix +1 -0
  57. data/sup.gemspec +2 -0
  58. data/test/dummy_source.rb +6 -0
  59. data/test/fixtures/embedded-message-rfc6532.eml +33 -0
  60. data/test/fixtures/invalid-date.eml +8 -0
  61. data/test/gnupg_test_home/private-keys-v1.d/26C05E44706A8E230B3255BB9532B34DC9420232.key +42 -0
  62. data/test/gnupg_test_home/private-keys-v1.d/D187ADC90EC4DEB7047678EAA37E33A53A465D47.key +5 -0
  63. data/test/gnupg_test_home/private-keys-v1.d/FB2D9BD3B1BE90B5BCF697781F8404224B0FCF5B.key +5 -0
  64. data/test/gnupg_test_home/pubring.gpg +0 -0
  65. data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
  66. data/test/gnupg_test_home/receiver_secring.gpg +0 -0
  67. data/test/gnupg_test_home/regen_keys.sh +11 -2
  68. data/test/gnupg_test_home/secring.gpg +0 -0
  69. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -20
  70. data/test/integration/test_maildir.rb +18 -1
  71. data/test/integration/test_mbox.rb +13 -0
  72. data/test/integration/test_sup-sync-back-maildir.rb +40 -0
  73. data/test/test_crypto.rb +108 -71
  74. data/test/test_header_parsing.rb +2 -2
  75. data/test/test_message.rb +42 -0
  76. data/test/unit/test_contact.rb +1 -1
  77. data/test/unit/test_edit_message_mode.rb +94 -0
  78. data/test/unit/test_person.rb +3 -3
  79. data/test/unit/test_rmail_message.rb +36 -0
  80. data/test/unit/util/test_string.rb +3 -3
  81. metadata +73 -5
  82. data/test/gnupg_test_home/private-keys-v1.d/306D2EE90FF0014B5B9FD07E265C751791674140.key +0 -0
data/test/test_crypto.rb CHANGED
@@ -38,14 +38,10 @@ class TestCryptoManager < Minitest::Test
38
38
  @path = Dir.mktmpdir
39
39
  Redwood::HookManager.init File.join(@path, 'hooks')
40
40
 
41
- am = {:default=> {name: "test", email: @from_email, alternates: [@from_email_ecc]}}
41
+ am = {:default=> {name: +"test", email: @from_email.dup, alternates: [@from_email_ecc.dup]}}
42
42
  Redwood::AccountManager.init am
43
43
 
44
44
  Redwood::CryptoManager.init
45
-
46
- if not CryptoManager.have_crypto?
47
- warn "No crypto set up, crypto will not be tested. Reason: #{CryptoManager.not_working_reason}"
48
- end
49
45
  end
50
46
 
51
47
  def teardown
@@ -58,90 +54,132 @@ class TestCryptoManager < Minitest::Test
58
54
  end
59
55
 
60
56
  def test_sign
61
- if CryptoManager.have_crypto? then
62
- signed = CryptoManager.sign @from_email,@to_email,"ABCDEFG"
63
- assert_instance_of RMail::Message, signed
64
- assert_equal("multipart/signed; protocol=application/pgp-signature; micalg=pgp-sha256",
65
- signed.header["Content-Type"])
66
- assert_equal "ABCDEFG", signed.body[0]
67
- assert signed.body[1].body.length > 0 , "signature length must be > 0"
68
- assert (signed.body[1].body.include? "-----BEGIN PGP SIGNATURE-----") , "Expecting PGP armored data"
69
- end
57
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
58
+
59
+ signed = CryptoManager.sign @from_email,@to_email,"ABCDEFG"
60
+ assert_instance_of RMail::Message, signed
61
+ assert_equal("multipart/signed; protocol=application/pgp-signature; micalg=pgp-sha256",
62
+ signed.header["Content-Type"])
63
+ assert_equal "ABCDEFG", signed.body[0]
64
+ assert signed.body[1].body.length > 0 , "signature length must be > 0"
65
+ assert (signed.body[1].body.include? "-----BEGIN PGP SIGNATURE-----") , "Expecting PGP armored data"
70
66
  end
71
67
 
72
68
  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
69
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
70
+
71
+ body = RMail::Message.new
72
+ body.header["Content-Disposition"] = +"inline"
73
+ body.body = "ABCDEFG"
74
+ payload = RMail::Message.new
75
+ payload.header["MIME-Version"] = +"1.0"
76
+ payload.add_part body
77
+ payload.add_part RMail::Message.make_attachment "attachment", "text/plain", nil, "attachment.txt"
78
+ signed = CryptoManager.sign @from_email, @to_email, payload
79
+ ## The result is a multipart/signed containing a multipart/mixed.
80
+ ## There should be a MIME-Version header on the top-level
81
+ ## multipart/signed message, but *not* on the enclosed
82
+ ## multipart/mixed part.
83
+ assert_equal 1, signed.to_s.scan(/MIME-Version:/).size
88
84
  end
89
85
 
90
86
  def test_encrypt
91
- if CryptoManager.have_crypto? then
92
- encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
93
- assert_instance_of RMail::Message, encrypted
94
- assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
95
- end
87
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
88
+
89
+ encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
90
+ assert_instance_of RMail::Message, encrypted
91
+ assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
96
92
  end
97
93
 
98
94
  def test_sign_and_encrypt
99
- if CryptoManager.have_crypto? then
100
- encrypted = CryptoManager.sign_and_encrypt @from_email, [@to_email], "ABCDEFG"
101
- assert_instance_of RMail::Message, encrypted
102
- assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
103
- end
95
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
96
+
97
+ encrypted = CryptoManager.sign_and_encrypt @from_email, [@to_email], "ABCDEFG"
98
+ assert_instance_of RMail::Message, encrypted
99
+ assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
104
100
  end
105
101
 
106
102
  def test_decrypt
107
- if CryptoManager.have_crypto? then
108
- encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
109
- assert_instance_of RMail::Message, encrypted
110
- assert_instance_of String, (encrypted.body[1].body)
111
- decrypted = CryptoManager.decrypt encrypted.body[1], true
112
- assert_instance_of Array, decrypted
113
- assert_instance_of Chunk::CryptoNotice, decrypted[0]
114
- assert_instance_of Chunk::CryptoNotice, decrypted[1]
115
- assert_instance_of RMail::Message, decrypted[2]
116
- assert_equal "ABCDEFG" , decrypted[2].body
117
- end
103
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
104
+
105
+ encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
106
+ assert_instance_of RMail::Message, encrypted
107
+ assert_instance_of String, (encrypted.body[1].body)
108
+ decrypted = CryptoManager.decrypt encrypted.body[1], true
109
+ assert_instance_of Array, decrypted
110
+ assert_instance_of Chunk::CryptoNotice, decrypted[0]
111
+ assert_instance_of Chunk::CryptoNotice, decrypted[1]
112
+ assert_instance_of RMail::Message, decrypted[2]
113
+ assert_equal "ABCDEFG" , decrypted[2].body
114
+ end
115
+
116
+ def test_decrypt_and_verify
117
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
118
+
119
+ encrypted = CryptoManager.sign_and_encrypt @from_email, [@to_email], "ABCDEFG"
120
+ assert_instance_of RMail::Message, encrypted
121
+ assert_instance_of String, (encrypted.body[1].body)
122
+ decrypted = CryptoManager.decrypt encrypted.body[1], true
123
+ assert_instance_of Array, decrypted
124
+ assert_instance_of Chunk::CryptoNotice, decrypted[0]
125
+ assert_instance_of Chunk::CryptoNotice, decrypted[1]
126
+ assert_instance_of RMail::Message, decrypted[2]
127
+ assert_match(/^Signature made .* using RSA key ID 072B50BE/,
128
+ decrypted[1].lines[0])
129
+ assert_equal "Good signature from \"#{@from_email}\"", decrypted[1].lines[1]
130
+ assert_equal "ABCDEFG" , decrypted[2].body
131
+ end
132
+
133
+ def test_decrypt_and_verify_nondefault_key
134
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
135
+
136
+ encrypted = CryptoManager.sign_and_encrypt @from_email_ecc, [@to_email], "ABCDEFG"
137
+ assert_instance_of RMail::Message, encrypted
138
+ assert_instance_of String, (encrypted.body[1].body)
139
+ decrypted = CryptoManager.decrypt encrypted.body[1], true
140
+ assert_instance_of Array, decrypted
141
+ assert_instance_of Chunk::CryptoNotice, decrypted[0]
142
+ assert_instance_of Chunk::CryptoNotice, decrypted[1]
143
+ assert_instance_of RMail::Message, decrypted[2]
144
+ assert_match(/^Signature made .* key ID AC34B83C/, decrypted[1].lines[0])
145
+ assert_equal "Good signature from \"#{@from_email_ecc}\"", decrypted[1].lines[1]
146
+ assert_equal "ABCDEFG" , decrypted[2].body
118
147
  end
119
148
 
120
149
  def test_verify
121
- if CryptoManager.have_crypto?
122
- signed = CryptoManager.sign @from_email, @to_email, "ABCDEFG"
123
- assert_instance_of RMail::Message, signed
124
- assert_instance_of String, (signed.body[1].body)
125
- CryptoManager.verify signed.body[0], signed.body[1], true
126
- end
150
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
151
+
152
+ signed = CryptoManager.sign @from_email, @to_email, "ABCDEFG"
153
+ assert_instance_of RMail::Message, signed
154
+ assert_instance_of String, (signed.body[1].body)
155
+ chunk = CryptoManager.verify signed.body[0], signed.body[1], true
156
+ assert_instance_of Redwood::Chunk::CryptoNotice, chunk
157
+ assert_match(/^Signature made .* using RSA key ID 072B50BE/,
158
+ chunk.lines[0])
159
+ assert_equal "Good signature from \"#{@from_email}\"", chunk.lines[1]
127
160
  end
128
161
 
129
162
  def test_verify_unknown_keytype
130
- if CryptoManager.have_crypto?
131
- signed = CryptoManager.sign @from_email_ecc, @to_email, "ABCDEFG"
132
- assert_instance_of RMail::Message, signed
133
- assert_instance_of String, (signed.body[1].body)
134
- CryptoManager.verify signed.body[0], signed.body[1], true
135
- end
163
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
164
+
165
+ signed = CryptoManager.sign @from_email_ecc, @to_email, "ABCDEFG"
166
+ assert_instance_of RMail::Message, signed
167
+ assert_instance_of String, (signed.body[1].body)
168
+ chunk = CryptoManager.verify signed.body[0], signed.body[1], true
169
+ assert_instance_of Redwood::Chunk::CryptoNotice, chunk
170
+ assert_match(/^Signature made .* using unknown key type \(303\) key ID AC34B83C/,
171
+ chunk.lines[0])
172
+ assert_equal "Good signature from \"#{@from_email_ecc}\"", chunk.lines[1]
136
173
  end
137
174
 
138
175
  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
176
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
177
+
178
+ ## Generate a multipart/signed containing a multipart/mixed.
179
+ ## We will test verifying the generated signature below.
180
+ ## Importantly, the inner multipart/mixed does *not* have a
181
+ ## MIME-Version header because it is not a top-level message.
182
+ payload = RMail::Parser.read <<EOS
145
183
  Content-Type: multipart/mixed; boundary="=-1652088224-7794-561531-1825-1-="
146
184
 
147
185
 
@@ -156,9 +194,8 @@ Content-Type: text/plain; name="attachment.txt"
156
194
  attachment
157
195
  --=-1652088224-7794-561531-1825-1-=--
158
196
  EOS
159
- signed = CryptoManager.sign @from_email_ecc, @to_email, payload
160
- CryptoManager.verify payload, signed.body[1], true
161
- end
197
+ signed = CryptoManager.sign @from_email_ecc, @to_email, payload
198
+ CryptoManager.verify payload, signed.body[1], true
162
199
  end
163
200
  end
164
201
 
@@ -4,10 +4,10 @@ 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')
data/test/test_message.rb CHANGED
@@ -344,6 +344,35 @@ class TestMessage < Minitest::Test
344
344
  assert_equal("Second line.", chunks[2].lines[1])
345
345
  end
346
346
 
347
+ def test_embedded_message_rfc6532
348
+ source = DummySource.new("sup-test://test_embedded_message_rfc6532")
349
+ source.messages = [ fixture_path("embedded-message-rfc6532.eml") ]
350
+
351
+ sup_message = Message.build_from_source(source, 0)
352
+
353
+ chunks = sup_message.load_from_source!
354
+ assert_equal(3, chunks.length)
355
+
356
+ assert_equal("Email with embedded message", sup_message.subj)
357
+
358
+ assert(chunks[0].is_a? Redwood::Chunk::Text)
359
+ assert_equal("Example outer message.", chunks[0].lines[0])
360
+
361
+ assert(chunks[1].is_a? Redwood::Chunk::EnclosedMessage)
362
+ assert_equal(4, chunks[1].lines.length)
363
+ assert_equal("From: Embed sender <embed@example.com>", chunks[1].lines[0])
364
+ assert_equal("To: rcpt2 <rcpt2@example.invalid>", chunks[1].lines[1])
365
+ assert_equal("Date: ", chunks[1].lines[2][0..5])
366
+ assert_equal(
367
+ Time.rfc2822("Sun, 12 May 2024 17:34:29 +1000"),
368
+ Time.rfc2822(chunks[1].lines[2][6..-1])
369
+ )
370
+ assert_equal("Subject: Embedded subject line with emoji ✨", chunks[1].lines[3])
371
+
372
+ assert(chunks[2].is_a? Redwood::Chunk::Text)
373
+ assert_equal("Example embedded message, with UTF-8 headers.", chunks[2].lines[0])
374
+ end
375
+
347
376
  def test_malicious_attachment_names
348
377
  source = DummySource.new("sup-test://test_blank_header_lines")
349
378
  source.messages = [ fixture_path('malicious-attachment-names.eml') ]
@@ -359,6 +388,19 @@ class TestMessage < Minitest::Test
359
388
  fn = chunks[3].safe_filename
360
389
  assert_equal(fn, File.basename(fn))
361
390
  end
391
+
392
+ def test_invalid_date_header
393
+ fallback_date = Time.utc 2024, 5, 12, 15, 5, 56
394
+ source = DummySource.new("sup-test://test_invalid_date_header")
395
+ source.messages = [ fixture_path("invalid-date.eml") ]
396
+ source.fallback_date = fallback_date
397
+
398
+ sup_message = Message.build_from_source(source, 0)
399
+ sup_message.load_from_source!
400
+
401
+ assert_equal(fallback_date, sup_message.date)
402
+ end
403
+
362
404
  # TODO: test different error cases, malformed messages etc.
363
405
 
364
406
  # TODO: test different quoting styles, see that they are all divided
@@ -6,7 +6,7 @@ module Redwood
6
6
  class TestContact < Minitest::Test
7
7
  def setup
8
8
  @contact = ContactManager.init(File.expand_path("../../fixtures/contacts.txt", __FILE__))
9
- @person = Person.new "Terrible Name", "terrible@name.com"
9
+ @person = Person.new (+"Terrible Name"), (+"terrible@name.com")
10
10
  end
11
11
 
12
12
  def teardown
@@ -0,0 +1,94 @@
1
+ require "test_helper"
2
+
3
+ require "sup"
4
+
5
+ class DummySelector
6
+ attr_accessor :val
7
+ def initialize val
8
+ @val = val
9
+ end
10
+ end
11
+
12
+ class DummyCryptoManager
13
+ def have_crypto?; true; end
14
+ def sign from, to, payload
15
+ envelope = RMail::Message.new
16
+ envelope.header["Content-Type"] = +"multipart/signed; protocol=testdummy"
17
+ envelope.add_part payload
18
+ envelope
19
+ end
20
+ end
21
+
22
+ class TestEditMessageMode < Minitest::Test
23
+ def setup
24
+ $config = {}
25
+ @path = Dir.mktmpdir
26
+ Redwood::HookManager.init File.join(@path, "hooks")
27
+ Redwood::AccountManager.init :default => {name: +"test", email: +"sender@example.invalid"}
28
+ Redwood::CryptoManager.instance_variable_set :@instance, DummyCryptoManager.new
29
+ end
30
+
31
+ def teardown
32
+ Redwood::CryptoManager.deinstantiate!
33
+ Redwood::AccountManager.deinstantiate!
34
+ Redwood::HookManager.deinstantiate!
35
+ FileUtils.rm_r @path
36
+ $config = nil
37
+ end
38
+
39
+ def test_attachment_content_transfer_encoding
40
+ ## RMail::Message#make_attachment will choose
41
+ ## Content-Transfer-Encoding: 8bit for a CSV file.
42
+ ## If we're not GPG signing or encrypting then the attachment will be sent
43
+ ## as is. Note this assumes the SMTP servers in the delivery path all
44
+ ## support the 8BITMIME extension.
45
+ attachment_content = "löl,\ntest,\n"
46
+ attachment_filename = File.join @path, "dummy.csv"
47
+ File.write attachment_filename, attachment_content
48
+
49
+ opts = {
50
+ :header => {
51
+ "From" => +"sender@example.invalid",
52
+ "To" => +"recip@example.invalid",
53
+ },
54
+ :attachments => {
55
+ "dummy.csv" => RMail::Message.make_file_attachment(attachment_filename),
56
+ },
57
+ }
58
+ mode = Redwood::EditMessageMode.new opts
59
+
60
+ msg = mode.send :build_message, Time.now
61
+ attachment = msg.part(1)
62
+ assert_equal attachment_content, attachment.body
63
+ assert_equal "8bit", attachment.header["Content-Transfer-Encoding"]
64
+ end
65
+
66
+ def test_attachment_content_transfer_encoding_signed
67
+ attachment_filename = File.join @path, "dummy.csv"
68
+ ## Include some high bytes in the attachment contents in order to
69
+ ## exercise quote-printable transfer encoding.
70
+ File.write attachment_filename, "löl,\ntest,\n"
71
+
72
+ opts = {
73
+ :header => {
74
+ "From" => +"sender@example.invalid",
75
+ "To" => +"recip@example.invalid",
76
+ },
77
+ :attachments => {
78
+ "dummy.csv" => RMail::Message.make_file_attachment(attachment_filename),
79
+ },
80
+ }
81
+ mode = Redwood::EditMessageMode.new opts
82
+ mode.instance_variable_set :@crypto_selector, DummySelector.new(:sign)
83
+
84
+ msg = mode.send :build_message, Time.now
85
+ ## The outermost message is a (fake) multipart/signed created by DummyCryptoManager#send.
86
+ ## Inside that we have our inline message at index 0 and CSV attachment at index 1.
87
+ attachment = msg.part(0).part(1)
88
+ ## The attachment should have been re-encoded as quoted-printable for GPG signing.
89
+ assert_equal "l=C3=B6l,\ntest,\n", attachment.body
90
+ ## There shouldn't be multiple Content-Transfer-Encoding headers.
91
+ ## This was: https://github.com/sup-heliotrope/sup/issues/502
92
+ assert_equal ["quoted-printable"], attachment.header.fetch_all("Content-Transfer-Encoding")
93
+ end
94
+ end
@@ -5,12 +5,12 @@ module Redwood
5
5
 
6
6
  class TestPerson < Minitest::Test
7
7
  def setup
8
- @person = Person.new("Thomassen, Bob", "bob@thomassen.com")
9
- @no_name = Person.new(nil, "alice@alice.com")
8
+ @person = Person.new(+"Thomassen, Bob", +"bob@thomassen.com")
9
+ @no_name = Person.new(nil, +"alice@alice.com")
10
10
  end
11
11
 
12
12
  def test_email_must_be_supplied
13
- assert_raises (ArgumentError) { Person.new("Alice", nil) }
13
+ assert_raises (ArgumentError) { Person.new(+"Alice", nil) }
14
14
  end
15
15
 
16
16
  def test_to_string
@@ -0,0 +1,36 @@
1
+ require "test_helper"
2
+
3
+ require "sup"
4
+
5
+ class TestRMailMessage < Minitest::Test
6
+ def setup
7
+ @path = Dir.mktmpdir
8
+ end
9
+
10
+ def teardown
11
+ FileUtils.rm_r @path
12
+ end
13
+
14
+ def test_make_file_attachment
15
+ filename = File.join @path, "test.html"
16
+ File.write filename, "<html></html>"
17
+
18
+ a = RMail::Message.make_file_attachment(filename)
19
+ assert_equal "text/html; name=\"test.html\"", a.header["Content-Type"]
20
+ assert_equal "attachment; filename=\"test.html\"", a.header["Content-Disposition"]
21
+ assert_equal "8bit", a.header["Content-Transfer-Encoding"]
22
+ end
23
+
24
+ def test_make_file_attachment_text_with_long_lines
25
+ filename = File.join @path, "test.html"
26
+ File.write filename, "a" * 1023
27
+
28
+ a = RMail::Message.make_file_attachment(filename)
29
+ assert_equal "text/html; name=\"test.html\"", a.header["Content-Type"]
30
+ assert_equal "attachment; filename=\"test.html\"", a.header["Content-Disposition"]
31
+ assert_equal "quoted-printable", a.header["Content-Transfer-Encoding"]
32
+
33
+ qp_encoded = ("a" * 73 + "=\n") * 14 + "a=\n"
34
+ assert_equal qp_encoded, a.body
35
+ end
36
+ end
@@ -18,7 +18,7 @@ describe "Sup's String extension" do
18
18
 
19
19
  it "calculates display length of a string" do
20
20
  data.each do |(str, length)|
21
- assert_equal length, str.display_length
21
+ assert_equal length, str.dup.display_length
22
22
  end
23
23
  end
24
24
  end
@@ -36,7 +36,7 @@ describe "Sup's String extension" do
36
36
 
37
37
  it "slices string by display length" do
38
38
  data.each do |(str, length, sliced)|
39
- assert_equal sliced, str.slice_by_display_length(length)
39
+ assert_equal sliced, str.dup.slice_by_display_length(length)
40
40
  end
41
41
  end
42
42
  end
@@ -56,7 +56,7 @@ describe "Sup's String extension" do
56
56
 
57
57
  it "wraps string by display length" do
58
58
  data.each do |(str, length, wrapped)|
59
- assert_equal wrapped, str.wrap(length)
59
+ assert_equal wrapped, str.dup.wrap(length)
60
60
  end
61
61
  end
62
62
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sup
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: '1.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Morgan
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2022-05-23 00:00:00.000000000 Z
14
+ date: 2025-04-21 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: ncursesw
@@ -159,6 +159,34 @@ dependencies:
159
159
  - - ">="
160
160
  - !ruby/object:Gem::Version
161
161
  version: '0'
162
+ - !ruby/object:Gem::Dependency
163
+ name: benchmark
164
+ requirement: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ type: :runtime
170
+ prerelease: false
171
+ version_requirements: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ - !ruby/object:Gem::Dependency
177
+ name: fiddle
178
+ requirement: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ type: :runtime
184
+ prerelease: false
185
+ version_requirements: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
162
190
  - !ruby/object:Gem::Dependency
163
191
  name: bundler
164
192
  requirement: !ruby/object:Gem::Requirement
@@ -322,6 +350,31 @@ files:
322
350
  - contrib/colorpicker.rb
323
351
  - contrib/completion/_sup.bash
324
352
  - contrib/completion/_sup.zsh
353
+ - contrib/nix/Gemfile
354
+ - contrib/nix/Gemfile.lock
355
+ - contrib/nix/README
356
+ - contrib/nix/gem-install-shell.nix
357
+ - contrib/nix/gemset.nix
358
+ - contrib/nix/ruby2.4-Gemfile.lock
359
+ - contrib/nix/ruby2.4-gemset.nix
360
+ - contrib/nix/ruby2.4-shell.nix
361
+ - contrib/nix/ruby2.5-Gemfile.lock
362
+ - contrib/nix/ruby2.5-gemset.nix
363
+ - contrib/nix/ruby2.5-shell.nix
364
+ - contrib/nix/ruby2.6-Gemfile.lock
365
+ - contrib/nix/ruby2.6-gemset.nix
366
+ - contrib/nix/ruby2.6-shell.nix
367
+ - contrib/nix/ruby2.7-Gemfile.lock
368
+ - contrib/nix/ruby2.7-gemset.nix
369
+ - contrib/nix/ruby2.7-shell.nix
370
+ - contrib/nix/ruby3.0-Gemfile.lock
371
+ - contrib/nix/ruby3.0-gemset.nix
372
+ - contrib/nix/ruby3.0-shell.nix
373
+ - contrib/nix/ruby3.1-shell.nix
374
+ - contrib/nix/ruby3.2-shell.nix
375
+ - contrib/nix/ruby3.3-shell.nix
376
+ - contrib/nix/ruby3.4-shell.nix
377
+ - contrib/nix/test-all-rubies.sh
325
378
  - devel/console.sh
326
379
  - devel/count-loc.sh
327
380
  - devel/load-index.rb
@@ -408,13 +461,16 @@ files:
408
461
  - man/sup-sync.1
409
462
  - man/sup-tweak-labels.1
410
463
  - man/sup.1
464
+ - shell.nix
411
465
  - sup.gemspec
412
466
  - test/dummy_source.rb
413
467
  - test/fixtures/bad-content-transfer-encoding-1.eml
414
468
  - test/fixtures/binary-content-transfer-encoding-2.eml
415
469
  - test/fixtures/blank-header-fields.eml
416
470
  - test/fixtures/contacts.txt
471
+ - test/fixtures/embedded-message-rfc6532.eml
417
472
  - test/fixtures/embedded-message.eml
473
+ - test/fixtures/invalid-date.eml
418
474
  - test/fixtures/mailing-list-header.eml
419
475
  - test/fixtures/malicious-attachment-names.eml
420
476
  - test/fixtures/missing-from-to.eml
@@ -431,7 +487,9 @@ files:
431
487
  - test/fixtures/zimbra-quote-with-bottom-post.eml
432
488
  - test/gnupg_test_home/.gpg-v21-migrated
433
489
  - test/gnupg_test_home/gpg.conf
434
- - test/gnupg_test_home/private-keys-v1.d/306D2EE90FF0014B5B9FD07E265C751791674140.key
490
+ - test/gnupg_test_home/private-keys-v1.d/26C05E44706A8E230B3255BB9532B34DC9420232.key
491
+ - test/gnupg_test_home/private-keys-v1.d/D187ADC90EC4DEB7047678EAA37E33A53A465D47.key
492
+ - test/gnupg_test_home/private-keys-v1.d/FB2D9BD3B1BE90B5BCF697781F8404224B0FCF5B.key
435
493
  - test/gnupg_test_home/pubring.gpg
436
494
  - test/gnupg_test_home/receiver_pubring.gpg
437
495
  - test/gnupg_test_home/receiver_secring.gpg
@@ -441,6 +499,7 @@ files:
441
499
  - test/integration/test_maildir.rb
442
500
  - test/integration/test_mbox.rb
443
501
  - test/integration/test_sup-add.rb
502
+ - test/integration/test_sup-sync-back-maildir.rb
444
503
  - test/test_crypto.rb
445
504
  - test/test_header_parsing.rb
446
505
  - test/test_helper.rb
@@ -449,9 +508,11 @@ files:
449
508
  - test/test_yaml_regressions.rb
450
509
  - test/unit/service/test_label_service.rb
451
510
  - test/unit/test_contact.rb
511
+ - test/unit/test_edit_message_mode.rb
452
512
  - test/unit/test_horizontal_selector.rb
453
513
  - test/unit/test_locale_fiddler.rb
454
514
  - test/unit/test_person.rb
515
+ - test/unit/test_rmail_message.rb
455
516
  - test/unit/util/test_query.rb
456
517
  - test/unit/util/test_string.rb
457
518
  - test/unit/util/test_uri.rb
@@ -482,7 +543,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
482
543
  - !ruby/object:Gem::Version
483
544
  version: '0'
484
545
  requirements: []
485
- rubygems_version: 3.2.22
546
+ rubygems_version: 3.5.22
486
547
  signing_key:
487
548
  specification_version: 4
488
549
  summary: A console-based email client with the best features of GMail, mutt and Emacs
@@ -492,7 +553,9 @@ test_files:
492
553
  - test/fixtures/binary-content-transfer-encoding-2.eml
493
554
  - test/fixtures/blank-header-fields.eml
494
555
  - test/fixtures/contacts.txt
556
+ - test/fixtures/embedded-message-rfc6532.eml
495
557
  - test/fixtures/embedded-message.eml
558
+ - test/fixtures/invalid-date.eml
496
559
  - test/fixtures/mailing-list-header.eml
497
560
  - test/fixtures/malicious-attachment-names.eml
498
561
  - test/fixtures/missing-from-to.eml
@@ -509,7 +572,9 @@ test_files:
509
572
  - test/fixtures/zimbra-quote-with-bottom-post.eml
510
573
  - test/gnupg_test_home/.gpg-v21-migrated
511
574
  - test/gnupg_test_home/gpg.conf
512
- - test/gnupg_test_home/private-keys-v1.d/306D2EE90FF0014B5B9FD07E265C751791674140.key
575
+ - test/gnupg_test_home/private-keys-v1.d/26C05E44706A8E230B3255BB9532B34DC9420232.key
576
+ - test/gnupg_test_home/private-keys-v1.d/D187ADC90EC4DEB7047678EAA37E33A53A465D47.key
577
+ - test/gnupg_test_home/private-keys-v1.d/FB2D9BD3B1BE90B5BCF697781F8404224B0FCF5B.key
513
578
  - test/gnupg_test_home/pubring.gpg
514
579
  - test/gnupg_test_home/receiver_pubring.gpg
515
580
  - test/gnupg_test_home/receiver_secring.gpg
@@ -519,6 +584,7 @@ test_files:
519
584
  - test/integration/test_maildir.rb
520
585
  - test/integration/test_mbox.rb
521
586
  - test/integration/test_sup-add.rb
587
+ - test/integration/test_sup-sync-back-maildir.rb
522
588
  - test/test_crypto.rb
523
589
  - test/test_header_parsing.rb
524
590
  - test/test_helper.rb
@@ -527,9 +593,11 @@ test_files:
527
593
  - test/test_yaml_regressions.rb
528
594
  - test/unit/service/test_label_service.rb
529
595
  - test/unit/test_contact.rb
596
+ - test/unit/test_edit_message_mode.rb
530
597
  - test/unit/test_horizontal_selector.rb
531
598
  - test/unit/test_locale_fiddler.rb
532
599
  - test/unit/test_person.rb
600
+ - test/unit/test_rmail_message.rb
533
601
  - test/unit/util/test_query.rb
534
602
  - test/unit/util/test_string.rb
535
603
  - test/unit/util/test_uri.rb