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.
- checksums.yaml +4 -4
- data/.github/workflows/checks.yml +19 -4
- data/.gitmodules +1 -0
- data/.rubocop.yml +1 -1
- data/History.txt +33 -0
- data/Manifest.txt +21 -2
- data/README.md +5 -3
- data/Rakefile +1 -1
- data/bin/sup +5 -3
- data/contrib/nix/Gemfile +2 -0
- data/contrib/nix/Gemfile.lock +62 -34
- data/contrib/nix/gem-install-shell.nix +2 -0
- data/contrib/nix/gemset.nix +130 -56
- data/contrib/nix/ruby2.4-Gemfile.lock +6 -2
- data/contrib/nix/ruby2.4-gemset.nix +24 -4
- data/contrib/nix/ruby2.4-shell.nix +2 -6
- data/contrib/nix/ruby2.5-Gemfile.lock +6 -2
- data/contrib/nix/ruby2.5-gemset.nix +24 -4
- data/contrib/nix/ruby2.5-shell.nix +2 -6
- data/contrib/nix/ruby2.6-Gemfile.lock +6 -2
- data/contrib/nix/ruby2.6-gemset.nix +24 -4
- data/contrib/nix/ruby2.6-shell.nix +2 -6
- data/contrib/nix/ruby2.7-Gemfile.lock +91 -0
- data/contrib/nix/ruby2.7-gemset.nix +359 -0
- data/contrib/nix/ruby2.7-shell.nix +2 -11
- data/contrib/nix/ruby3.0-Gemfile.lock +91 -0
- data/contrib/nix/ruby3.0-gemset.nix +359 -0
- data/contrib/nix/ruby3.0-shell.nix +2 -11
- data/contrib/nix/ruby3.1-Gemfile.lock +101 -0
- data/contrib/nix/ruby3.1-gemset.nix +391 -0
- data/contrib/nix/ruby3.1-shell.nix +3 -12
- data/contrib/nix/ruby3.2-Gemfile.lock +101 -0
- data/contrib/nix/ruby3.2-gemset.nix +391 -0
- data/contrib/nix/ruby3.2-shell.nix +3 -12
- data/contrib/nix/ruby3.3-shell.nix +1 -10
- data/contrib/nix/ruby3.4-shell.nix +27 -0
- data/contrib/nix/ruby4.0-shell.nix +40 -0
- data/contrib/nix/test-all-rubies.sh +1 -1
- data/lib/sup/account.rb +2 -0
- data/lib/sup/buffer.rb +1 -0
- data/lib/sup/contact.rb +3 -4
- data/lib/sup/crypto.rb +7 -5
- data/lib/sup/draft.rb +15 -11
- data/lib/sup/index.rb +8 -4
- data/lib/sup/maildir.rb +4 -0
- data/lib/sup/mbox.rb +25 -7
- data/lib/sup/message.rb +13 -10
- data/lib/sup/message_chunks.rb +0 -24
- data/lib/sup/mode.rb +1 -0
- data/lib/sup/modes/contact_list_mode.rb +0 -1
- data/lib/sup/modes/edit_message_mode.rb +6 -6
- data/lib/sup/modes/label_search_results_mode.rb +1 -2
- data/lib/sup/modes/line_cursor_mode.rb +22 -20
- data/lib/sup/modes/search_results_mode.rb +0 -1
- data/lib/sup/modes/thread_view_mode.rb +1 -2
- data/lib/sup/rfc2047.rb +5 -2
- data/lib/sup/source.rb +2 -0
- data/lib/sup/util.rb +9 -2
- data/lib/sup/version.rb +1 -1
- data/lib/sup.rb +1 -1
- data/man/sup-add.1 +18 -18
- data/man/sup-config.1 +12 -12
- data/man/sup-dump.1 +15 -14
- data/man/sup-import-dump.1 +24 -24
- data/man/sup-psych-ify-config-files.1 +11 -11
- data/man/sup-recover-sources.1 +20 -20
- data/man/sup-sync-back-maildir.1 +24 -23
- data/man/sup-sync.1 +35 -35
- data/man/sup-tweak-labels.1 +27 -26
- data/man/sup.1 +27 -27
- data/sup.gemspec +3 -1
- data/test/dummy_buffer.rb +34 -0
- data/test/dummy_source.rb +6 -0
- data/test/fixtures/contacts.txt +2 -1
- data/test/fixtures/embedded-message-rfc6532.eml +33 -0
- data/test/fixtures/invalid-date.eml +8 -0
- data/test/fixtures/rfc2047-header-encoding.eml +1 -1
- data/test/gnupg_test_home/private-keys-v1.d/26C05E44706A8E230B3255BB9532B34DC9420232.key +42 -0
- data/test/gnupg_test_home/private-keys-v1.d/D187ADC90EC4DEB7047678EAA37E33A53A465D47.key +5 -0
- data/test/gnupg_test_home/private-keys-v1.d/FB2D9BD3B1BE90B5BCF697781F8404224B0FCF5B.key +5 -0
- data/test/gnupg_test_home/pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_secring.gpg +0 -0
- data/test/gnupg_test_home/regen_keys.sh +11 -2
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -20
- data/test/integration/test_draft.rb +128 -0
- data/test/integration/test_maildir.rb +17 -1
- data/test/integration/test_mbox.rb +12 -0
- data/test/integration/test_sup-add.rb +4 -0
- data/test/test_crypto.rb +114 -72
- data/test/test_message.rb +43 -0
- data/test/unit/test_contact.rb +16 -4
- data/test/unit/test_edit_message_mode.rb +99 -0
- data/test/unit/test_index.rb +65 -0
- data/test/unit/test_line_cursor_mode.rb +208 -0
- data/test/unit/test_person.rb +3 -3
- data/test/unit/test_rmail_message.rb +36 -0
- data/test/unit/util/test_string.rb +3 -3
- metadata +64 -9
- data/shell.nix +0 -1
- 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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
data/test/unit/test_contact.rb
CHANGED
|
@@ -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
|
-
|
|
20
|
-
|
|
21
|
-
assert_equal @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
|