sup 1.2 → 1.4

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/checks.yml +19 -4
  3. data/.gitmodules +1 -0
  4. data/.rubocop.yml +1 -1
  5. data/History.txt +33 -0
  6. data/Manifest.txt +21 -2
  7. data/README.md +5 -3
  8. data/Rakefile +1 -1
  9. data/bin/sup +5 -3
  10. data/contrib/nix/Gemfile +2 -0
  11. data/contrib/nix/Gemfile.lock +62 -34
  12. data/contrib/nix/gem-install-shell.nix +2 -0
  13. data/contrib/nix/gemset.nix +130 -56
  14. data/contrib/nix/ruby2.4-Gemfile.lock +6 -2
  15. data/contrib/nix/ruby2.4-gemset.nix +24 -4
  16. data/contrib/nix/ruby2.4-shell.nix +2 -6
  17. data/contrib/nix/ruby2.5-Gemfile.lock +6 -2
  18. data/contrib/nix/ruby2.5-gemset.nix +24 -4
  19. data/contrib/nix/ruby2.5-shell.nix +2 -6
  20. data/contrib/nix/ruby2.6-Gemfile.lock +6 -2
  21. data/contrib/nix/ruby2.6-gemset.nix +24 -4
  22. data/contrib/nix/ruby2.6-shell.nix +2 -6
  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 +2 -11
  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 +2 -11
  29. data/contrib/nix/ruby3.1-Gemfile.lock +101 -0
  30. data/contrib/nix/ruby3.1-gemset.nix +391 -0
  31. data/contrib/nix/ruby3.1-shell.nix +3 -12
  32. data/contrib/nix/ruby3.2-Gemfile.lock +101 -0
  33. data/contrib/nix/ruby3.2-gemset.nix +391 -0
  34. data/contrib/nix/ruby3.2-shell.nix +3 -12
  35. data/contrib/nix/ruby3.3-shell.nix +1 -10
  36. data/contrib/nix/ruby3.4-shell.nix +27 -0
  37. data/contrib/nix/ruby4.0-shell.nix +40 -0
  38. data/contrib/nix/test-all-rubies.sh +1 -1
  39. data/lib/sup/account.rb +2 -0
  40. data/lib/sup/buffer.rb +1 -0
  41. data/lib/sup/contact.rb +3 -4
  42. data/lib/sup/crypto.rb +7 -5
  43. data/lib/sup/draft.rb +15 -11
  44. data/lib/sup/index.rb +8 -4
  45. data/lib/sup/maildir.rb +4 -0
  46. data/lib/sup/mbox.rb +25 -7
  47. data/lib/sup/message.rb +13 -10
  48. data/lib/sup/message_chunks.rb +0 -24
  49. data/lib/sup/mode.rb +1 -0
  50. data/lib/sup/modes/contact_list_mode.rb +0 -1
  51. data/lib/sup/modes/edit_message_mode.rb +6 -6
  52. data/lib/sup/modes/label_search_results_mode.rb +1 -2
  53. data/lib/sup/modes/line_cursor_mode.rb +22 -20
  54. data/lib/sup/modes/search_results_mode.rb +0 -1
  55. data/lib/sup/modes/thread_view_mode.rb +1 -2
  56. data/lib/sup/rfc2047.rb +5 -2
  57. data/lib/sup/source.rb +2 -0
  58. data/lib/sup/util.rb +9 -2
  59. data/lib/sup/version.rb +1 -1
  60. data/lib/sup.rb +1 -1
  61. data/man/sup-add.1 +18 -18
  62. data/man/sup-config.1 +12 -12
  63. data/man/sup-dump.1 +15 -14
  64. data/man/sup-import-dump.1 +24 -24
  65. data/man/sup-psych-ify-config-files.1 +11 -11
  66. data/man/sup-recover-sources.1 +20 -20
  67. data/man/sup-sync-back-maildir.1 +24 -23
  68. data/man/sup-sync.1 +35 -35
  69. data/man/sup-tweak-labels.1 +27 -26
  70. data/man/sup.1 +27 -27
  71. data/sup.gemspec +3 -1
  72. data/test/dummy_buffer.rb +34 -0
  73. data/test/dummy_source.rb +6 -0
  74. data/test/fixtures/contacts.txt +2 -1
  75. data/test/fixtures/embedded-message-rfc6532.eml +33 -0
  76. data/test/fixtures/invalid-date.eml +8 -0
  77. data/test/fixtures/rfc2047-header-encoding.eml +1 -1
  78. data/test/gnupg_test_home/private-keys-v1.d/26C05E44706A8E230B3255BB9532B34DC9420232.key +42 -0
  79. data/test/gnupg_test_home/private-keys-v1.d/D187ADC90EC4DEB7047678EAA37E33A53A465D47.key +5 -0
  80. data/test/gnupg_test_home/private-keys-v1.d/FB2D9BD3B1BE90B5BCF697781F8404224B0FCF5B.key +5 -0
  81. data/test/gnupg_test_home/pubring.gpg +0 -0
  82. data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
  83. data/test/gnupg_test_home/receiver_secring.gpg +0 -0
  84. data/test/gnupg_test_home/regen_keys.sh +11 -2
  85. data/test/gnupg_test_home/secring.gpg +0 -0
  86. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -20
  87. data/test/integration/test_draft.rb +128 -0
  88. data/test/integration/test_maildir.rb +17 -1
  89. data/test/integration/test_mbox.rb +12 -0
  90. data/test/integration/test_sup-add.rb +4 -0
  91. data/test/test_crypto.rb +114 -72
  92. data/test/test_message.rb +43 -0
  93. data/test/unit/test_contact.rb +16 -4
  94. data/test/unit/test_edit_message_mode.rb +99 -0
  95. data/test/unit/test_index.rb +65 -0
  96. data/test/unit/test_line_cursor_mode.rb +208 -0
  97. data/test/unit/test_person.rb +3 -3
  98. data/test/unit/test_rmail_message.rb +36 -0
  99. data/test/unit/util/test_string.rb +3 -3
  100. metadata +64 -9
  101. data/shell.nix +0 -1
  102. data/test/gnupg_test_home/private-keys-v1.d/306D2EE90FF0014B5B9FD07E265C751791674140.key +0 -0
data/test/test_crypto.rb CHANGED
@@ -38,14 +38,15 @@ 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]}}
42
- Redwood::AccountManager.init am
41
+ account = {
42
+ :name => +"test",
43
+ :email => @from_email.dup,
44
+ :alternates => [@from_email_ecc.dup],
45
+ :sendmail => "/bin/false",
46
+ }
47
+ Redwood::AccountManager.init :default => account
43
48
 
44
49
  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
50
  end
50
51
 
51
52
  def teardown
@@ -58,90 +59,132 @@ class TestCryptoManager < Minitest::Test
58
59
  end
59
60
 
60
61
  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
62
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
63
+
64
+ signed = CryptoManager.sign @from_email,@to_email,"ABCDEFG"
65
+ assert_instance_of RMail::Message, signed
66
+ assert_equal("multipart/signed; protocol=application/pgp-signature; micalg=pgp-sha256",
67
+ signed.header["Content-Type"])
68
+ assert_equal "ABCDEFG", signed.body[0]
69
+ assert signed.body[1].body.length > 0 , "signature length must be > 0"
70
+ assert (signed.body[1].body.include? "-----BEGIN PGP SIGNATURE-----") , "Expecting PGP armored data"
70
71
  end
71
72
 
72
73
  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
74
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
75
+
76
+ body = RMail::Message.new
77
+ body.header["Content-Disposition"] = +"inline"
78
+ body.body = "ABCDEFG"
79
+ payload = RMail::Message.new
80
+ payload.header["MIME-Version"] = +"1.0"
81
+ payload.add_part body
82
+ payload.add_part RMail::Message.make_attachment "attachment", "text/plain", nil, "attachment.txt"
83
+ signed = CryptoManager.sign @from_email, @to_email, payload
84
+ ## The result is a multipart/signed containing a multipart/mixed.
85
+ ## There should be a MIME-Version header on the top-level
86
+ ## multipart/signed message, but *not* on the enclosed
87
+ ## multipart/mixed part.
88
+ assert_equal 1, signed.to_s.scan(/MIME-Version:/).size
88
89
  end
89
90
 
90
91
  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
92
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
93
+
94
+ encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
95
+ assert_instance_of RMail::Message, encrypted
96
+ assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
96
97
  end
97
98
 
98
99
  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
100
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
101
+
102
+ encrypted = CryptoManager.sign_and_encrypt @from_email, [@to_email], "ABCDEFG"
103
+ assert_instance_of RMail::Message, encrypted
104
+ assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
104
105
  end
105
106
 
106
107
  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
108
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
109
+
110
+ encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
111
+ assert_instance_of RMail::Message, encrypted
112
+ assert_instance_of String, (encrypted.body[1].body)
113
+ decrypted = CryptoManager.decrypt encrypted.body[1], true
114
+ assert_instance_of Array, decrypted
115
+ assert_instance_of Chunk::CryptoNotice, decrypted[0]
116
+ assert_instance_of Chunk::CryptoNotice, decrypted[1]
117
+ assert_instance_of RMail::Message, decrypted[2]
118
+ assert_equal "ABCDEFG" , decrypted[2].body
119
+ end
120
+
121
+ def test_decrypt_and_verify
122
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
123
+
124
+ encrypted = CryptoManager.sign_and_encrypt @from_email, [@to_email], "ABCDEFG"
125
+ assert_instance_of RMail::Message, encrypted
126
+ assert_instance_of String, (encrypted.body[1].body)
127
+ decrypted = CryptoManager.decrypt encrypted.body[1], true
128
+ assert_instance_of Array, decrypted
129
+ assert_instance_of Chunk::CryptoNotice, decrypted[0]
130
+ assert_instance_of Chunk::CryptoNotice, decrypted[1]
131
+ assert_instance_of RMail::Message, decrypted[2]
132
+ assert_match(/^Signature made .* using RSA key ID 072B50BE/,
133
+ decrypted[1].lines[0])
134
+ assert_equal "Good signature from \"#{@from_email}\"", decrypted[1].lines[1]
135
+ assert_equal "ABCDEFG" , decrypted[2].body
136
+ end
137
+
138
+ def test_decrypt_and_verify_nondefault_key
139
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
140
+
141
+ encrypted = CryptoManager.sign_and_encrypt @from_email_ecc, [@to_email], "ABCDEFG"
142
+ assert_instance_of RMail::Message, encrypted
143
+ assert_instance_of String, (encrypted.body[1].body)
144
+ decrypted = CryptoManager.decrypt encrypted.body[1], true
145
+ assert_instance_of Array, decrypted
146
+ assert_instance_of Chunk::CryptoNotice, decrypted[0]
147
+ assert_instance_of Chunk::CryptoNotice, decrypted[1]
148
+ assert_instance_of RMail::Message, decrypted[2]
149
+ assert_match(/^Signature made .* key ID AC34B83C/, decrypted[1].lines[0])
150
+ assert_equal "Good signature from \"#{@from_email_ecc}\"", decrypted[1].lines[1]
151
+ assert_equal "ABCDEFG" , decrypted[2].body
118
152
  end
119
153
 
120
154
  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
155
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
156
+
157
+ signed = CryptoManager.sign @from_email, @to_email, "ABCDEFG"
158
+ assert_instance_of RMail::Message, signed
159
+ assert_instance_of String, (signed.body[1].body)
160
+ chunk = CryptoManager.verify signed.body[0], signed.body[1], true
161
+ assert_instance_of Redwood::Chunk::CryptoNotice, chunk
162
+ assert_match(/^Signature made .* using RSA key ID 072B50BE/,
163
+ chunk.lines[0])
164
+ assert_equal "Good signature from \"#{@from_email}\"", chunk.lines[1]
127
165
  end
128
166
 
129
167
  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
168
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
169
+
170
+ signed = CryptoManager.sign @from_email_ecc, @to_email, "ABCDEFG"
171
+ assert_instance_of RMail::Message, signed
172
+ assert_instance_of String, (signed.body[1].body)
173
+ chunk = CryptoManager.verify signed.body[0], signed.body[1], true
174
+ assert_instance_of Redwood::Chunk::CryptoNotice, chunk
175
+ assert_match(/^Signature made .* using unknown key type \(303\) key ID AC34B83C/,
176
+ chunk.lines[0])
177
+ assert_equal "Good signature from \"#{@from_email_ecc}\"", chunk.lines[1]
136
178
  end
137
179
 
138
180
  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
181
+ skip CryptoManager.not_working_reason if not CryptoManager.have_crypto?
182
+
183
+ ## Generate a multipart/signed containing a multipart/mixed.
184
+ ## We will test verifying the generated signature below.
185
+ ## Importantly, the inner multipart/mixed does *not* have a
186
+ ## MIME-Version header because it is not a top-level message.
187
+ payload = RMail::Parser.read <<EOS
145
188
  Content-Type: multipart/mixed; boundary="=-1652088224-7794-561531-1825-1-="
146
189
 
147
190
 
@@ -156,9 +199,8 @@ Content-Type: text/plain; name="attachment.txt"
156
199
  attachment
157
200
  --=-1652088224-7794-561531-1825-1-=--
158
201
  EOS
159
- signed = CryptoManager.sign @from_email_ecc, @to_email, payload
160
- CryptoManager.verify payload, signed.body[1], true
161
- end
202
+ signed = CryptoManager.sign @from_email_ecc, @to_email, payload
203
+ CryptoManager.verify payload, signed.body[1], true
162
204
  end
163
205
  end
164
206
 
data/test/test_message.rb CHANGED
@@ -253,6 +253,7 @@ class TestMessage < Minitest::Test
253
253
  "bad: =?UTF16?q?badcharsetname?==?US-ASCII?b?/w?=" +
254
254
  "=?UTF-7?Q?=41=6D=65=72=69=63=61=E2=80=99=73?=",
255
255
  sup_message.subj)
256
+ assert_equal "=?utf-8?q?YouTube-tj=E4nst?=", sup_message.from.name
256
257
  end
257
258
 
258
259
  def test_nonascii_header
@@ -344,6 +345,35 @@ class TestMessage < Minitest::Test
344
345
  assert_equal("Second line.", chunks[2].lines[1])
345
346
  end
346
347
 
348
+ def test_embedded_message_rfc6532
349
+ source = DummySource.new("sup-test://test_embedded_message_rfc6532")
350
+ source.messages = [ fixture_path("embedded-message-rfc6532.eml") ]
351
+
352
+ sup_message = Message.build_from_source(source, 0)
353
+
354
+ chunks = sup_message.load_from_source!
355
+ assert_equal(3, chunks.length)
356
+
357
+ assert_equal("Email with embedded message", sup_message.subj)
358
+
359
+ assert(chunks[0].is_a? Redwood::Chunk::Text)
360
+ assert_equal("Example outer message.", chunks[0].lines[0])
361
+
362
+ assert(chunks[1].is_a? Redwood::Chunk::EnclosedMessage)
363
+ assert_equal(4, chunks[1].lines.length)
364
+ assert_equal("From: Embed sender <embed@example.com>", chunks[1].lines[0])
365
+ assert_equal("To: rcpt2 <rcpt2@example.invalid>", chunks[1].lines[1])
366
+ assert_equal("Date: ", chunks[1].lines[2][0..5])
367
+ assert_equal(
368
+ Time.rfc2822("Sun, 12 May 2024 17:34:29 +1000"),
369
+ Time.rfc2822(chunks[1].lines[2][6..-1])
370
+ )
371
+ assert_equal("Subject: Embedded subject line with emoji ✨", chunks[1].lines[3])
372
+
373
+ assert(chunks[2].is_a? Redwood::Chunk::Text)
374
+ assert_equal("Example embedded message, with UTF-8 headers.", chunks[2].lines[0])
375
+ end
376
+
347
377
  def test_malicious_attachment_names
348
378
  source = DummySource.new("sup-test://test_blank_header_lines")
349
379
  source.messages = [ fixture_path('malicious-attachment-names.eml') ]
@@ -359,6 +389,19 @@ class TestMessage < Minitest::Test
359
389
  fn = chunks[3].safe_filename
360
390
  assert_equal(fn, File.basename(fn))
361
391
  end
392
+
393
+ def test_invalid_date_header
394
+ fallback_date = Time.utc 2024, 5, 12, 15, 5, 56
395
+ source = DummySource.new("sup-test://test_invalid_date_header")
396
+ source.messages = [ fixture_path("invalid-date.eml") ]
397
+ source.fallback_date = fallback_date
398
+
399
+ sup_message = Message.build_from_source(source, 0)
400
+ sup_message.load_from_source!
401
+
402
+ assert_equal(fallback_date, sup_message.date)
403
+ end
404
+
362
405
  # TODO: test different error cases, malformed messages etc.
363
406
 
364
407
  # TODO: test different quoting styles, see that they are all divided
@@ -1,4 +1,5 @@
1
1
  require 'test_helper'
2
+ require 'sup'
2
3
  require 'sup/contact'
3
4
 
4
5
  module Redwood
@@ -6,7 +7,7 @@ module Redwood
6
7
  class TestContact < Minitest::Test
7
8
  def setup
8
9
  @contact = ContactManager.init(File.expand_path("../../fixtures/contacts.txt", __FILE__))
9
- @person = Person.new "Terrible Name", "terrible@name.com"
10
+ @person = Person.new (+"Terrible Name"), (+"terrible@name.com")
10
11
  end
11
12
 
12
13
  def teardown
@@ -16,9 +17,20 @@ class TestContact < Minitest::Test
16
17
 
17
18
  def test_contact_manager
18
19
  assert @contact
19
- ## 1 contact is imported from the fixture file.
20
- assert_equal 1, @contact.contacts.count
21
- assert_equal @contact.contact_for("RC").name, "Random Contact"
20
+
21
+ ## 2 contacts are imported from the fixture file.
22
+ assert_equal 2, @contact.contacts.count
23
+
24
+ rc = @contact.contact_for "RC"
25
+ assert_equal "Random Contact", rc.name
26
+ assert @contact.is_aliased_contact? rc
27
+ assert_equal "RC", @contact.alias_for(rc)
28
+
29
+ uc = @contact.person_for "unaliased@example.invalid"
30
+ refute @contact.is_aliased_contact? uc
31
+ assert_nil @contact.alias_for uc
32
+ assert_equal [rc, uc], @contact.contacts
33
+ assert_equal [rc], @contact.contacts_with_aliases
22
34
 
23
35
  assert_nil @contact.contact_for "TN"
24
36
  @contact.update_alias @person, "TN"
@@ -0,0 +1,99 @@
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
+ account = {
28
+ :name => +"test",
29
+ :email => +"sender@example.invalid",
30
+ :sendmail => "/bin/false",
31
+ }
32
+ Redwood::AccountManager.init :default => account
33
+ Redwood::CryptoManager.instance_variable_set :@instance, DummyCryptoManager.new
34
+ end
35
+
36
+ def teardown
37
+ Redwood::CryptoManager.deinstantiate!
38
+ Redwood::AccountManager.deinstantiate!
39
+ Redwood::HookManager.deinstantiate!
40
+ FileUtils.rm_r @path
41
+ $config = nil
42
+ end
43
+
44
+ def test_attachment_content_transfer_encoding
45
+ ## RMail::Message#make_attachment will choose
46
+ ## Content-Transfer-Encoding: 8bit for a CSV file.
47
+ ## If we're not GPG signing or encrypting then the attachment will be sent
48
+ ## as is. Note this assumes the SMTP servers in the delivery path all
49
+ ## support the 8BITMIME extension.
50
+ attachment_content = "löl,\ntest,\n"
51
+ attachment_filename = File.join @path, "dummy.csv"
52
+ File.write attachment_filename, attachment_content
53
+
54
+ opts = {
55
+ :header => {
56
+ "From" => +"sender@example.invalid",
57
+ "To" => +"recip@example.invalid",
58
+ },
59
+ :attachments => {
60
+ "dummy.csv" => RMail::Message.make_file_attachment(attachment_filename),
61
+ },
62
+ }
63
+ mode = Redwood::EditMessageMode.new opts
64
+
65
+ msg = mode.send :build_message, Time.now
66
+ attachment = msg.part(1)
67
+ assert_equal attachment_content, attachment.body
68
+ assert_equal "8bit", attachment.header["Content-Transfer-Encoding"]
69
+ end
70
+
71
+ def test_attachment_content_transfer_encoding_signed
72
+ attachment_filename = File.join @path, "dummy.csv"
73
+ ## Include some high bytes in the attachment contents in order to
74
+ ## exercise quote-printable transfer encoding.
75
+ File.write attachment_filename, "löl,\ntest,\n"
76
+
77
+ opts = {
78
+ :header => {
79
+ "From" => +"sender@example.invalid",
80
+ "To" => +"recip@example.invalid",
81
+ },
82
+ :attachments => {
83
+ "dummy.csv" => RMail::Message.make_file_attachment(attachment_filename),
84
+ },
85
+ }
86
+ mode = Redwood::EditMessageMode.new opts
87
+ mode.instance_variable_set :@crypto_selector, DummySelector.new(:sign)
88
+
89
+ msg = mode.send :build_message, Time.now
90
+ ## The outermost message is a (fake) multipart/signed created by DummyCryptoManager#send.
91
+ ## Inside that we have our inline message at index 0 and CSV attachment at index 1.
92
+ attachment = msg.part(0).part(1)
93
+ ## The attachment should have been re-encoded as quoted-printable for GPG signing.
94
+ assert_equal "l=C3=B6l,\ntest,\n", attachment.body
95
+ ## There shouldn't be multiple Content-Transfer-Encoding headers.
96
+ ## This was: https://github.com/sup-heliotrope/sup/issues/502
97
+ assert_equal ["quoted-printable"], attachment.header.fetch_all("Content-Transfer-Encoding")
98
+ end
99
+ end
@@ -0,0 +1,65 @@
1
+ require "minitest/mock"
2
+ require "test_helper"
3
+
4
+ require "sup"
5
+
6
+ class TestIndex < Minitest::Test
7
+ def setup
8
+ @path = Dir.mktmpdir
9
+ Redwood::start
10
+ Redwood::Logger.remove_sink $stderr
11
+ Redwood::Index.init @path
12
+ Redwood::Index.load
13
+ end
14
+
15
+ def teardown
16
+ Redwood::Index.save_index
17
+ ObjectSpace.each_object(Class).select {|a| a < Redwood::Singleton}.each do |klass|
18
+ klass.deinstantiate! unless klass == Redwood::Logger
19
+ end
20
+ FileUtils.rm_r @path
21
+ end
22
+
23
+ def with_fake_time &block
24
+ Time.stub :now, Time.utc(2000) do
25
+ ## Also stub Time.local to behave like Time.utc so that Chronic.parse
26
+ ## doesn't pick up the timezone of the test runner.
27
+ Time.stub :local, ->(*args) { Time.utc(*args) }, &block
28
+ end
29
+ end
30
+
31
+ def test_date_query_parsing
32
+ parsed = with_fake_time do
33
+ Redwood::Index.parse_query "after:yesterday"
34
+ end
35
+ expected_qobj = Xapian::Query.new(
36
+ Xapian::Query::OP_VALUE_RANGE,
37
+ Redwood::Index::DATE_VALUENO,
38
+ Xapian.sortable_serialise(Time.utc(2000, 1, 1).to_i),
39
+ Xapian.sortable_serialise(2**32),
40
+ )
41
+ assert_equal expected_qobj.description, parsed[:qobj].description
42
+
43
+ parsed = with_fake_time do
44
+ Redwood::Index.parse_query "before:yesterday"
45
+ end
46
+ expected_qobj = Xapian::Query.new(
47
+ Xapian::Query::OP_VALUE_RANGE,
48
+ Redwood::Index::DATE_VALUENO,
49
+ Xapian.sortable_serialise(0),
50
+ Xapian.sortable_serialise(Time.utc(2000, 1, 1).to_i),
51
+ )
52
+ assert_equal expected_qobj.description, parsed[:qobj].description
53
+
54
+ parsed = with_fake_time do
55
+ Redwood::Index.parse_query "during:yesterday"
56
+ end
57
+ expected_qobj = Xapian::Query.new(
58
+ Xapian::Query::OP_VALUE_RANGE,
59
+ Redwood::Index::DATE_VALUENO,
60
+ Xapian.sortable_serialise(Time.utc(1999, 12, 31).to_i),
61
+ Xapian.sortable_serialise(Time.utc(2000, 1, 1).to_i),
62
+ )
63
+ assert_equal expected_qobj.description, parsed[:qobj].description
64
+ end
65
+ end