sup 0.19.0 → 0.22.1
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/.gitignore +5 -0
- data/.gitmodules +3 -0
- data/.travis.yml +3 -2
- data/CONTRIBUTORS +19 -13
- data/Gemfile +4 -0
- data/History.txt +41 -0
- data/Rakefile +41 -1
- data/ReleaseNotes +17 -0
- data/bin/sup +5 -18
- data/bin/sup-add +1 -2
- data/bin/sup-config +0 -1
- data/bin/sup-dump +0 -1
- data/bin/sup-import-dump +1 -2
- data/bin/sup-sync +0 -1
- data/bin/sup-sync-back-maildir +1 -2
- data/bin/sup-tweak-labels +1 -2
- data/contrib/colorpicker.rb +0 -2
- data/contrib/completion/_sup.bash +102 -0
- data/devel/profile.rb +0 -1
- data/ext/mkrf_conf_xapian.rb +47 -0
- data/lib/sup.rb +9 -8
- data/lib/sup/buffer.rb +12 -0
- data/lib/sup/colormap.rb +5 -2
- data/lib/sup/contact.rb +4 -2
- data/lib/sup/crypto.rb +41 -8
- data/lib/sup/draft.rb +8 -8
- data/lib/sup/hook.rb +1 -1
- data/lib/sup/index.rb +2 -2
- data/lib/sup/label.rb +1 -1
- data/lib/sup/maildir.rb +16 -5
- data/lib/sup/mbox.rb +13 -5
- data/lib/sup/message.rb +17 -3
- data/lib/sup/message_chunks.rb +10 -2
- data/lib/sup/mode.rb +33 -28
- data/lib/sup/modes/edit_message_mode.rb +3 -2
- data/lib/sup/modes/forward_mode.rb +22 -3
- data/lib/sup/modes/line_cursor_mode.rb +1 -1
- data/lib/sup/modes/text_mode.rb +6 -1
- data/lib/sup/modes/thread_index_mode.rb +11 -1
- data/lib/sup/modes/thread_view_mode.rb +103 -9
- data/lib/sup/person.rb +68 -61
- data/lib/sup/search.rb +1 -1
- data/lib/sup/sent.rb +1 -1
- data/lib/sup/util.rb +1 -75
- data/lib/sup/util/locale_fiddler.rb +24 -0
- data/lib/sup/version.rb +1 -1
- data/sup.gemspec +22 -5
- data/test/{messages → fixtures}/bad-content-transfer-encoding-1.eml +0 -0
- data/test/{messages → fixtures}/binary-content-transfer-encoding-2.eml +0 -0
- data/test/fixtures/blank-header-fields.eml +71 -0
- data/test/fixtures/contacts.txt +1 -0
- data/test/fixtures/malicious-attachment-names.eml +55 -0
- data/test/fixtures/missing-from-to.eml +18 -0
- data/test/{messages → fixtures}/missing-line.eml +0 -0
- data/test/fixtures/multi-part-2.eml +72 -0
- data/test/fixtures/multi-part.eml +61 -0
- data/test/fixtures/no-body.eml +18 -0
- data/test/fixtures/simple-message.eml +29 -0
- data/test/gnupg_test_home/gpg.conf +2 -1
- data/test/gnupg_test_home/key1.gen +15 -0
- data/test/gnupg_test_home/key2.gen +15 -0
- data/test/gnupg_test_home/key_ecc.gen +13 -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 +38 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +22 -17
- data/test/integration/test_maildir.rb +75 -0
- data/test/integration/test_mbox.rb +69 -0
- data/test/test_crypto.rb +12 -2
- data/test/test_header_parsing.rb +1 -1
- data/test/test_helper.rb +6 -3
- data/test/test_message.rb +42 -342
- data/test/test_messages_dir.rb +4 -28
- data/test/test_yaml_regressions.rb +1 -1
- data/test/unit/test_contact.rb +33 -0
- data/test/unit/test_locale_fiddler.rb +15 -0
- data/test/unit/test_person.rb +37 -0
- metadata +108 -38
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
data/lib/sup/mbox.rb
CHANGED
@@ -19,16 +19,24 @@ class MBox < Source
|
|
19
19
|
case uri_or_fp
|
20
20
|
when String
|
21
21
|
@expanded_uri = Source.expand_filesystem_uri(uri_or_fp)
|
22
|
-
|
22
|
+
parts = @expanded_uri.match /^([a-zA-Z0-9]*:(\/\/)?)(.*)/
|
23
|
+
if parts
|
24
|
+
prefix = parts[1]
|
25
|
+
@path = parts[3]
|
26
|
+
uri = URI(prefix + URI.encode(@path, URI_ENCODE_CHARS))
|
27
|
+
else
|
28
|
+
uri = URI(URI.encode @expanded_uri, URI_ENCODE_CHARS)
|
29
|
+
@path = uri.path
|
30
|
+
end
|
31
|
+
|
23
32
|
raise ArgumentError, "not an mbox uri" unless uri.scheme == "mbox"
|
24
33
|
raise ArgumentError, "mbox URI ('#{uri}') cannot have a host: #{uri.host}" if uri.host
|
25
34
|
raise ArgumentError, "mbox URI must have a path component" unless uri.path
|
26
35
|
@f = nil
|
27
|
-
@path = uri.path
|
28
36
|
else
|
29
37
|
@f = uri_or_fp
|
30
38
|
@path = uri_or_fp.path
|
31
|
-
@expanded_uri = "mbox://#{@path}"
|
39
|
+
@expanded_uri = "mbox://#{URI.encode @path, URI_ENCODE_CHARS}"
|
32
40
|
end
|
33
41
|
|
34
42
|
super uri_or_fp, usual, archived, id
|
@@ -107,7 +115,7 @@ class MBox < Source
|
|
107
115
|
end
|
108
116
|
|
109
117
|
def store_message date, from_email, &block
|
110
|
-
need_blank = File.
|
118
|
+
need_blank = File.exist?(@path) && !File.zero?(@path)
|
111
119
|
File.open(@path, "ab") do |f|
|
112
120
|
f.puts if need_blank
|
113
121
|
f.puts "From #{from_email} #{date.asctime}"
|
@@ -172,7 +180,7 @@ class MBox < Source
|
|
172
180
|
time = $1
|
173
181
|
begin
|
174
182
|
## hack -- make Time.parse fail when trying to substitute values from Time.now
|
175
|
-
Time.parse time, 0
|
183
|
+
Time.parse time, Time.at(0)
|
176
184
|
true
|
177
185
|
rescue NoMethodError, ArgumentError
|
178
186
|
warn "found invalid date in potential mbox split line, not splitting: #{l.inspect}"
|
data/lib/sup/message.rb
CHANGED
@@ -268,9 +268,23 @@ class Message
|
|
268
268
|
debug "could not load message: #{location.inspect}, exception: #{e.inspect}"
|
269
269
|
|
270
270
|
[Chunk::Text.new(error_message.split("\n"))]
|
271
|
+
|
272
|
+
rescue Exception => e
|
273
|
+
|
274
|
+
warn "problem reading message #{id}"
|
275
|
+
debug "could not load message: #{location.inspect}, exception: #{e.inspect}"
|
276
|
+
|
277
|
+
raise e
|
278
|
+
|
271
279
|
end
|
272
280
|
end
|
273
281
|
|
282
|
+
def reload_from_source!
|
283
|
+
@chunks = nil
|
284
|
+
load_from_source!
|
285
|
+
end
|
286
|
+
|
287
|
+
|
274
288
|
def error_message
|
275
289
|
<<EOS
|
276
290
|
#@snippet...
|
@@ -327,17 +341,17 @@ EOS
|
|
327
341
|
to.map { |p| p.indexable_content },
|
328
342
|
cc.map { |p| p.indexable_content },
|
329
343
|
bcc.map { |p| p.indexable_content },
|
330
|
-
indexable_chunks.map { |c| c.lines },
|
344
|
+
indexable_chunks.map { |c| c.lines.map { |l| l.fix_encoding! } },
|
331
345
|
indexable_subject,
|
332
346
|
].flatten.compact.join " "
|
333
347
|
end
|
334
348
|
|
335
349
|
def indexable_body
|
336
|
-
indexable_chunks.map { |c| c.lines }.flatten.compact.join " "
|
350
|
+
indexable_chunks.map { |c| c.lines }.flatten.compact.map { |l| l.fix_encoding! }.join " "
|
337
351
|
end
|
338
352
|
|
339
353
|
def indexable_chunks
|
340
|
-
chunks.select { |c| c.
|
354
|
+
chunks.select { |c| c.indexable? } || []
|
341
355
|
end
|
342
356
|
|
343
357
|
def indexable_subject
|
data/lib/sup/message_chunks.rb
CHANGED
@@ -159,11 +159,13 @@ EOS
|
|
159
159
|
"Attachment: #{filename} (#{content_type}; #{@raw_content.size.to_human_size})"
|
160
160
|
end
|
161
161
|
end
|
162
|
+
def safe_filename; Shellwords.escape(@filename).gsub("/", "_") end
|
162
163
|
|
163
164
|
## an attachment is exapndable if we've managed to decode it into
|
164
165
|
## something we can display inline. otherwise, it's viewable.
|
165
166
|
def inlineable?; false end
|
166
167
|
def expandable?; !viewable? end
|
168
|
+
def indexable?; expandable? end
|
167
169
|
def initial_state; :open end
|
168
170
|
def viewable?; @lines.nil? end
|
169
171
|
def view_default! path
|
@@ -229,6 +231,7 @@ EOS
|
|
229
231
|
def inlineable?; true end
|
230
232
|
def quotable?; true end
|
231
233
|
def expandable?; false end
|
234
|
+
def indexable?; true end
|
232
235
|
def viewable?; false end
|
233
236
|
def color; :text_color end
|
234
237
|
end
|
@@ -242,6 +245,7 @@ EOS
|
|
242
245
|
def inlineable?; @lines.length == 1 end
|
243
246
|
def quotable?; true end
|
244
247
|
def expandable?; !inlineable? end
|
248
|
+
def indexable?; expandable? end
|
245
249
|
def viewable?; false end
|
246
250
|
|
247
251
|
def patina_color; :quote_patina_color end
|
@@ -258,6 +262,7 @@ EOS
|
|
258
262
|
def inlineable?; @lines.length == 1 end
|
259
263
|
def quotable?; false end
|
260
264
|
def expandable?; !inlineable? end
|
265
|
+
def indexable?; expandable? end
|
261
266
|
def viewable?; false end
|
262
267
|
|
263
268
|
def patina_color; :sig_patina_color end
|
@@ -291,6 +296,7 @@ EOS
|
|
291
296
|
def inlineable?; false end
|
292
297
|
def quotable?; false end
|
293
298
|
def expandable?; true end
|
299
|
+
def indexable?; true end
|
294
300
|
def initial_state; :closed end
|
295
301
|
def viewable?; false end
|
296
302
|
|
@@ -301,12 +307,13 @@ EOS
|
|
301
307
|
end
|
302
308
|
|
303
309
|
class CryptoNotice
|
304
|
-
attr_reader :lines, :status, :patina_text
|
310
|
+
attr_reader :lines, :status, :patina_text, :unknown_fingerprint
|
305
311
|
|
306
|
-
def initialize status, description, lines=[]
|
312
|
+
def initialize status, description, lines=[], unknown_fingerprint=nil
|
307
313
|
@status = status
|
308
314
|
@patina_text = description
|
309
315
|
@lines = lines
|
316
|
+
@unknown_fingerprint = unknown_fingerprint
|
310
317
|
end
|
311
318
|
|
312
319
|
def patina_color
|
@@ -322,6 +329,7 @@ EOS
|
|
322
329
|
def inlineable?; false end
|
323
330
|
def quotable?; false end
|
324
331
|
def expandable?; !@lines.empty? end
|
332
|
+
def indexable?; false end
|
325
333
|
def viewable?; false end
|
326
334
|
end
|
327
335
|
end
|
data/lib/sup/mode.rb
CHANGED
@@ -46,7 +46,7 @@ class Mode
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def resolve_input c
|
49
|
-
ancestors.each do |klass| # try all keymaps in order of ancestry
|
49
|
+
self.class.ancestors.each do |klass| # try all keymaps in order of ancestry
|
50
50
|
next unless @@keymaps.member?(klass)
|
51
51
|
action = BufferManager.resolve_input_with_keymap c, @@keymaps[klass]
|
52
52
|
return action if action
|
@@ -62,7 +62,7 @@ class Mode
|
|
62
62
|
|
63
63
|
def help_text
|
64
64
|
used_keys = {}
|
65
|
-
ancestors.map do |klass|
|
65
|
+
self.class.ancestors.map do |klass|
|
66
66
|
km = @@keymaps[klass] or next
|
67
67
|
title = "Keybindings from #{Mode.make_name klass.name}"
|
68
68
|
s = <<EOS
|
@@ -83,7 +83,7 @@ EOS
|
|
83
83
|
### helper functions
|
84
84
|
|
85
85
|
def save_to_file fn, talk=true
|
86
|
-
if File.
|
86
|
+
if File.exist? fn
|
87
87
|
unless BufferManager.ask_yes_or_no "File \"#{fn}\" exists. Overwrite?"
|
88
88
|
info "Not overwriting #{fn}"
|
89
89
|
return
|
@@ -102,37 +102,42 @@ EOS
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def pipe_to_process command
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
105
|
+
begin
|
106
|
+
Open3.popen3(command) do |input, output, error|
|
107
|
+
err, data, * = IO.select [error], [input], nil
|
108
|
+
|
109
|
+
unless err.empty?
|
110
|
+
message = err.first.read
|
111
|
+
if message =~ /^\s*$/
|
112
|
+
warn "error running #{command} (but no error message)"
|
113
|
+
BufferManager.flash "Error running #{command}!"
|
114
|
+
else
|
115
|
+
warn "error running #{command}: #{message}"
|
116
|
+
BufferManager.flash "Error: #{message}"
|
117
|
+
end
|
118
|
+
return nil, false
|
116
119
|
end
|
117
|
-
return
|
118
|
-
end
|
119
120
|
|
120
|
-
|
121
|
-
|
121
|
+
data = data.first
|
122
|
+
data.sync = false # buffer input
|
122
123
|
|
123
|
-
|
124
|
-
|
124
|
+
yield data
|
125
|
+
data.close # output will block unless input is closed
|
125
126
|
|
126
|
-
|
127
|
-
|
128
|
-
|
127
|
+
## BUG?: shows errors or output but not both....
|
128
|
+
data, * = IO.select [output, error], nil, nil
|
129
|
+
data = data.first
|
129
130
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
if data.eof
|
132
|
+
BufferManager.flash "'#{command}' done!"
|
133
|
+
return nil, true
|
134
|
+
else
|
135
|
+
return data.read, true
|
136
|
+
end
|
135
137
|
end
|
138
|
+
rescue Errno::ENOENT
|
139
|
+
# If the command is invalid
|
140
|
+
return nil, false
|
136
141
|
end
|
137
142
|
end
|
138
143
|
end
|
@@ -20,6 +20,7 @@ Variables:
|
|
20
20
|
to the raw headers for the message. E.g., header["From"],
|
21
21
|
header["To"], etc.
|
22
22
|
from_email: the email part of the From: line, or nil if empty
|
23
|
+
message_id: the unique message id of the message
|
23
24
|
Return value:
|
24
25
|
A string (multi-line ok) containing the text of the signature, or nil to
|
25
26
|
use the default signature, or :none for no signature.
|
@@ -688,7 +689,7 @@ private
|
|
688
689
|
from_email = p && p.email
|
689
690
|
|
690
691
|
## first run the hook
|
691
|
-
hook_sig = HookManager.run "signature", :header => @header, :from_email => from_email
|
692
|
+
hook_sig = HookManager.run "signature", :header => @header, :from_email => from_email, :message_id => @message_id
|
692
693
|
|
693
694
|
return [] if hook_sig == :none
|
694
695
|
return ["", "-- "] + hook_sig.split("\n") if hook_sig
|
@@ -698,7 +699,7 @@ private
|
|
698
699
|
sigfn = (AccountManager.account_for(from_email) ||
|
699
700
|
AccountManager.default_account).signature
|
700
701
|
|
701
|
-
if sigfn && File.
|
702
|
+
if sigfn && File.exist?(sigfn)
|
702
703
|
["", "-- "] + File.readlines(sigfn).map { |l| l.chomp }
|
703
704
|
else
|
704
705
|
[]
|
@@ -1,6 +1,17 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class ForwardMode < EditMessageMode
|
4
|
+
|
5
|
+
HookManager.register "forward-attribution", <<EOS
|
6
|
+
Generates the attribution for the forwarded message
|
7
|
+
(["--- Begin forwarded message from John Doe ---",
|
8
|
+
"--- End forwarded message ---"])
|
9
|
+
Variables:
|
10
|
+
message: a message object representing the message being replied to
|
11
|
+
(useful values include message.from.mediumname and message.date)
|
12
|
+
Return value:
|
13
|
+
A list containing two strings: the text of the begin line and the text of the end line
|
14
|
+
EOS
|
4
15
|
## TODO: share some of this with reply-mode
|
5
16
|
def initialize opts={}
|
6
17
|
header = {
|
@@ -65,9 +76,17 @@ class ForwardMode < EditMessageMode
|
|
65
76
|
protected
|
66
77
|
|
67
78
|
def forward_body_lines m
|
68
|
-
|
69
|
-
|
70
|
-
|
79
|
+
attribution = HookManager.run("forward-attribution", :message => m) || default_attribution(m)
|
80
|
+
attribution[0,1] +
|
81
|
+
m.quotable_header_lines +
|
82
|
+
[""] +
|
83
|
+
m.quotable_body_lines +
|
84
|
+
attribution[1,1]
|
85
|
+
end
|
86
|
+
|
87
|
+
def default_attribution m
|
88
|
+
["--- Begin forwarded message from #{m.from.mediumname} ---",
|
89
|
+
"--- End forwarded message ---"]
|
71
90
|
end
|
72
91
|
|
73
92
|
def send_message
|
data/lib/sup/modes/text_mode.rb
CHANGED
@@ -24,10 +24,15 @@ class TextMode < ScrollMode
|
|
24
24
|
command = BufferManager.ask(:shell, "pipe command: ")
|
25
25
|
return if command.nil? || command.empty?
|
26
26
|
|
27
|
-
output = pipe_to_process(command) do |stream|
|
27
|
+
output, success = pipe_to_process(command) do |stream|
|
28
28
|
@text.each { |l| stream.puts l }
|
29
29
|
end
|
30
30
|
|
31
|
+
unless success
|
32
|
+
BufferManager.flash "Invalid command: '#{command}' is not an executable"
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
31
36
|
if output
|
32
37
|
BufferManager.spawn "Output of '#{command}'", TextMode.new(output.ascii)
|
33
38
|
else
|
@@ -856,6 +856,12 @@ protected
|
|
856
856
|
need_update = false
|
857
857
|
|
858
858
|
@mutex.synchronize do
|
859
|
+
# and certainly not sure why this happens..
|
860
|
+
#
|
861
|
+
# probably a race condition between thread modification and updating
|
862
|
+
# going on.
|
863
|
+
return if @threads[l].empty?
|
864
|
+
|
859
865
|
@size_widgets[l] = size_widget_for_thread @threads[l]
|
860
866
|
@date_widgets[l] = date_widget_for_thread @threads[l]
|
861
867
|
|
@@ -1020,7 +1026,11 @@ private
|
|
1020
1026
|
end
|
1021
1027
|
|
1022
1028
|
def from_width
|
1023
|
-
|
1029
|
+
if buffer
|
1030
|
+
[(buffer.content_width.to_f * 0.2).to_i, MIN_FROM_WIDTH].max
|
1031
|
+
else
|
1032
|
+
MIN_FROM_WIDTH # not sure why the buffer is gone
|
1033
|
+
end
|
1024
1034
|
end
|
1025
1035
|
|
1026
1036
|
def initialize_threads
|
@@ -42,6 +42,14 @@ Return value:
|
|
42
42
|
None.
|
43
43
|
EOS
|
44
44
|
|
45
|
+
HookManager.register "goto", <<EOS
|
46
|
+
Open the uri given as a parameter.
|
47
|
+
Variables:
|
48
|
+
uri: The uri
|
49
|
+
Return value:
|
50
|
+
None.
|
51
|
+
EOS
|
52
|
+
|
45
53
|
register_keymap do |k|
|
46
54
|
k.add :toggle_detailed_header, "Toggle detailed header", 'h'
|
47
55
|
k.add :show_header, "Show full message header", 'H'
|
@@ -80,6 +88,9 @@ EOS
|
|
80
88
|
k.add :kill_and_next, "Kill this thread, kill buffer, and view next", '&'
|
81
89
|
k.add :toggle_wrap, "Toggle wrapping of text", 'w'
|
82
90
|
|
91
|
+
k.add :goto_uri, "Goto uri under cursor", 'g'
|
92
|
+
k.add :fetch_and_verify, "Fetch the PGP key on poolserver and re-verify message", "v"
|
93
|
+
|
83
94
|
k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read:", '.' do |kk|
|
84
95
|
kk.add :archive_and_kill, "Archive this thread and kill buffer", 'a'
|
85
96
|
kk.add :delete_and_kill, "Delete this thread and kill buffer", 'd'
|
@@ -214,10 +225,24 @@ EOS
|
|
214
225
|
|
215
226
|
def unsubscribe_from_list
|
216
227
|
m = @message_lines[curpos] or return
|
217
|
-
|
228
|
+
BufferManager.flash "Can't find List-Unsubscribe header for this message." unless m.list_unsubscribe
|
229
|
+
|
230
|
+
if m.list_unsubscribe =~ /<mailto:(.*?)(\?subject=(.*?))?>/
|
218
231
|
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => ($3 || "unsubscribe")
|
219
|
-
|
220
|
-
|
232
|
+
elsif m.list_unsubscribe =~ /<(http.*)?>/
|
233
|
+
unless HookManager.enabled? "goto"
|
234
|
+
BufferManager.flash "You must add a goto.rb hook before you can goto an unsubscribe URI."
|
235
|
+
return
|
236
|
+
end
|
237
|
+
|
238
|
+
begin
|
239
|
+
u = URI.parse($1)
|
240
|
+
rescue URI::InvalidURIError => e
|
241
|
+
BufferManager.flash("Invalid unsubscribe link")
|
242
|
+
return
|
243
|
+
end
|
244
|
+
|
245
|
+
HookManager.run "goto", :uri => Shellwords.escape(u.to_s)
|
221
246
|
end
|
222
247
|
end
|
223
248
|
|
@@ -364,7 +389,7 @@ EOS
|
|
364
389
|
when Chunk::Attachment
|
365
390
|
default_dir = $config[:default_attachment_save_dir]
|
366
391
|
default_dir = ENV["HOME"] if default_dir.nil? || default_dir.empty?
|
367
|
-
default_fn = File.expand_path File.join(default_dir, chunk.
|
392
|
+
default_fn = File.expand_path File.join(default_dir, chunk.safe_filename)
|
368
393
|
fn = BufferManager.ask_for_filename :filename, "Save attachment to file or directory: ", default_fn, true
|
369
394
|
|
370
395
|
# if user selects directory use file name from message
|
@@ -393,7 +418,7 @@ EOS
|
|
393
418
|
num_errors = 0
|
394
419
|
m.chunks.each do |chunk|
|
395
420
|
next unless chunk.is_a?(Chunk::Attachment)
|
396
|
-
fn = File.join(folder, chunk.
|
421
|
+
fn = File.join(folder, chunk.safe_filename)
|
397
422
|
num_errors += 1 unless save_to_file(fn, false) { |f| f.print chunk.raw_content }
|
398
423
|
num += 1
|
399
424
|
end
|
@@ -698,7 +723,7 @@ EOS
|
|
698
723
|
command = BufferManager.ask(:shell, "pipe command: ")
|
699
724
|
return if command.nil? || command.empty?
|
700
725
|
|
701
|
-
output = pipe_to_process(command) do |stream|
|
726
|
+
output, success = pipe_to_process(command) do |stream|
|
702
727
|
if chunk
|
703
728
|
stream.print chunk.raw_content
|
704
729
|
else
|
@@ -706,6 +731,11 @@ EOS
|
|
706
731
|
end
|
707
732
|
end
|
708
733
|
|
734
|
+
unless success
|
735
|
+
BufferManager.flash "Invalid command: '#{command}' is not an executable"
|
736
|
+
return
|
737
|
+
end
|
738
|
+
|
709
739
|
if output
|
710
740
|
BufferManager.spawn "Output of '#{command}'", TextMode.new(output.ascii)
|
711
741
|
else
|
@@ -722,6 +752,69 @@ EOS
|
|
722
752
|
[user_labels, super].join(" -- ")
|
723
753
|
end
|
724
754
|
|
755
|
+
def goto_uri
|
756
|
+
unless (chunk = @chunk_lines[curpos])
|
757
|
+
BufferManager.flash "No URI found."
|
758
|
+
return
|
759
|
+
end
|
760
|
+
unless HookManager.enabled? "goto"
|
761
|
+
BufferManager.flash "You must add a goto.rb hook before you can goto a URI."
|
762
|
+
return
|
763
|
+
end
|
764
|
+
|
765
|
+
# @text is a list of lines with this format:
|
766
|
+
# [
|
767
|
+
# [[:text_color, "Some text"]]
|
768
|
+
# [[:text_color, " continued here"]]
|
769
|
+
# ]
|
770
|
+
|
771
|
+
linetext = @text.slice(curpos, @text.length).flatten(1)
|
772
|
+
.take_while{|d| d[0] == :text_color and d[1].strip != ""} # Only take up to the first "" alone on its line
|
773
|
+
.map{|d| d[1].strip}.join("").strip
|
774
|
+
|
775
|
+
found = false
|
776
|
+
(linetext || "").scan(URI::regexp).each do |matches|
|
777
|
+
begin
|
778
|
+
link = $& # ruby magic: $& is the whole regexp match
|
779
|
+
u = URI.parse(link)
|
780
|
+
next unless u.absolute?
|
781
|
+
next unless ["http", "https"].include?(u.scheme)
|
782
|
+
|
783
|
+
reallink = Shellwords.escape(u.to_s)
|
784
|
+
BufferManager.flash "Going to #{reallink} ..."
|
785
|
+
HookManager.run "goto", :uri => reallink
|
786
|
+
BufferManager.completely_redraw_screen
|
787
|
+
found = true
|
788
|
+
|
789
|
+
rescue URI::InvalidURIError => e
|
790
|
+
debug "not a uri: #{e}"
|
791
|
+
# Do nothing, this is an ok flow
|
792
|
+
end
|
793
|
+
end
|
794
|
+
BufferManager.flash "No URI found." unless found
|
795
|
+
end
|
796
|
+
|
797
|
+
def fetch_and_verify
|
798
|
+
message = @message_lines[curpos]
|
799
|
+
crypto_chunk = message.chunks.select {|chunk| chunk.is_a?(Chunk::CryptoNotice)}.first
|
800
|
+
return unless crypto_chunk
|
801
|
+
return unless crypto_chunk.unknown_fingerprint
|
802
|
+
|
803
|
+
BufferManager.flash "Retrieving key #{crypto_chunk.unknown_fingerprint} ..."
|
804
|
+
|
805
|
+
error = CryptoManager.retrieve crypto_chunk.unknown_fingerprint
|
806
|
+
|
807
|
+
if error
|
808
|
+
BufferManager.flash "Couldn't retrieve key: #{error.to_s}"
|
809
|
+
else
|
810
|
+
BufferManager.flash "Key #{crypto_chunk.unknown_fingerprint} successfully retrieved !"
|
811
|
+
end
|
812
|
+
|
813
|
+
# Re-trigger gpg verification
|
814
|
+
message.reload_from_source!
|
815
|
+
update
|
816
|
+
end
|
817
|
+
|
725
818
|
private
|
726
819
|
|
727
820
|
def initial_state_for m
|
@@ -847,9 +940,10 @@ private
|
|
847
940
|
addressee_lines += format_person_list " Bcc: ", m.bcc
|
848
941
|
end
|
849
942
|
|
850
|
-
headers =
|
851
|
-
|
852
|
-
|
943
|
+
headers = {
|
944
|
+
"Date" => "#{m.date.to_message_nice_s} (#{m.date.to_nice_distance_s})",
|
945
|
+
"Subject" => m.subj
|
946
|
+
}
|
853
947
|
|
854
948
|
show_labels = @thread.labels - LabelManager::HIDDEN_RESERVED_LABELS
|
855
949
|
unless show_labels.empty?
|