sup 0.19.0 → 0.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.gitmodules +3 -0
  4. data/.travis.yml +3 -2
  5. data/CONTRIBUTORS +19 -13
  6. data/Gemfile +4 -0
  7. data/History.txt +41 -0
  8. data/Rakefile +41 -1
  9. data/ReleaseNotes +17 -0
  10. data/bin/sup +5 -18
  11. data/bin/sup-add +1 -2
  12. data/bin/sup-config +0 -1
  13. data/bin/sup-dump +0 -1
  14. data/bin/sup-import-dump +1 -2
  15. data/bin/sup-sync +0 -1
  16. data/bin/sup-sync-back-maildir +1 -2
  17. data/bin/sup-tweak-labels +1 -2
  18. data/contrib/colorpicker.rb +0 -2
  19. data/contrib/completion/_sup.bash +102 -0
  20. data/devel/profile.rb +0 -1
  21. data/ext/mkrf_conf_xapian.rb +47 -0
  22. data/lib/sup.rb +9 -8
  23. data/lib/sup/buffer.rb +12 -0
  24. data/lib/sup/colormap.rb +5 -2
  25. data/lib/sup/contact.rb +4 -2
  26. data/lib/sup/crypto.rb +41 -8
  27. data/lib/sup/draft.rb +8 -8
  28. data/lib/sup/hook.rb +1 -1
  29. data/lib/sup/index.rb +2 -2
  30. data/lib/sup/label.rb +1 -1
  31. data/lib/sup/maildir.rb +16 -5
  32. data/lib/sup/mbox.rb +13 -5
  33. data/lib/sup/message.rb +17 -3
  34. data/lib/sup/message_chunks.rb +10 -2
  35. data/lib/sup/mode.rb +33 -28
  36. data/lib/sup/modes/edit_message_mode.rb +3 -2
  37. data/lib/sup/modes/forward_mode.rb +22 -3
  38. data/lib/sup/modes/line_cursor_mode.rb +1 -1
  39. data/lib/sup/modes/text_mode.rb +6 -1
  40. data/lib/sup/modes/thread_index_mode.rb +11 -1
  41. data/lib/sup/modes/thread_view_mode.rb +103 -9
  42. data/lib/sup/person.rb +68 -61
  43. data/lib/sup/search.rb +1 -1
  44. data/lib/sup/sent.rb +1 -1
  45. data/lib/sup/util.rb +1 -75
  46. data/lib/sup/util/locale_fiddler.rb +24 -0
  47. data/lib/sup/version.rb +1 -1
  48. data/sup.gemspec +22 -5
  49. data/test/{messages → fixtures}/bad-content-transfer-encoding-1.eml +0 -0
  50. data/test/{messages → fixtures}/binary-content-transfer-encoding-2.eml +0 -0
  51. data/test/fixtures/blank-header-fields.eml +71 -0
  52. data/test/fixtures/contacts.txt +1 -0
  53. data/test/fixtures/malicious-attachment-names.eml +55 -0
  54. data/test/fixtures/missing-from-to.eml +18 -0
  55. data/test/{messages → fixtures}/missing-line.eml +0 -0
  56. data/test/fixtures/multi-part-2.eml +72 -0
  57. data/test/fixtures/multi-part.eml +61 -0
  58. data/test/fixtures/no-body.eml +18 -0
  59. data/test/fixtures/simple-message.eml +29 -0
  60. data/test/gnupg_test_home/gpg.conf +2 -1
  61. data/test/gnupg_test_home/key1.gen +15 -0
  62. data/test/gnupg_test_home/key2.gen +15 -0
  63. data/test/gnupg_test_home/key_ecc.gen +13 -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 +38 -0
  68. data/test/gnupg_test_home/secring.gpg +0 -0
  69. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +22 -17
  70. data/test/integration/test_maildir.rb +75 -0
  71. data/test/integration/test_mbox.rb +69 -0
  72. data/test/test_crypto.rb +12 -2
  73. data/test/test_header_parsing.rb +1 -1
  74. data/test/test_helper.rb +6 -3
  75. data/test/test_message.rb +42 -342
  76. data/test/test_messages_dir.rb +4 -28
  77. data/test/test_yaml_regressions.rb +1 -1
  78. data/test/unit/test_contact.rb +33 -0
  79. data/test/unit/test_locale_fiddler.rb +15 -0
  80. data/test/unit/test_person.rb +37 -0
  81. metadata +108 -38
  82. data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
  83. data/test/gnupg_test_home/trustdb.gpg +0 -0
@@ -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
- uri = URI(@expanded_uri)
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.exists?(@path) && !File.zero?(@path)
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}"
@@ -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.is_a? Chunk::Text } || []
354
+ chunks.select { |c| c.indexable? } || []
341
355
  end
342
356
 
343
357
  def indexable_subject
@@ -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
@@ -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.exists? fn
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
- Open3.popen3(command) do |input, output, error|
106
- err, data, * = IO.select [error], [input], nil
107
-
108
- unless err.empty?
109
- message = err.first.read
110
- if message =~ /^\s*$/
111
- warn "error running #{command} (but no error message)"
112
- BufferManager.flash "Error running #{command}!"
113
- else
114
- warn "error running #{command}: #{message}"
115
- BufferManager.flash "Error: #{message}"
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
- data = data.first
121
- data.sync = false # buffer input
121
+ data = data.first
122
+ data.sync = false # buffer input
122
123
 
123
- yield data
124
- data.close # output will block unless input is closed
124
+ yield data
125
+ data.close # output will block unless input is closed
125
126
 
126
- ## BUG?: shows errors or output but not both....
127
- data, * = IO.select [output, error], nil, nil
128
- data = data.first
127
+ ## BUG?: shows errors or output but not both....
128
+ data, * = IO.select [output, error], nil, nil
129
+ data = data.first
129
130
 
130
- if data.eof
131
- BufferManager.flash "'#{command}' done!"
132
- nil
133
- else
134
- data.read
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.exists?(sigfn)
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
- ["--- Begin forwarded message from #{m.from.mediumname} ---"] +
69
- m.quotable_header_lines + [""] + m.quotable_body_lines +
70
- ["--- End forwarded message ---"]
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
@@ -65,7 +65,7 @@ protected
65
65
  def set_cursor_pos p
66
66
  return if @curpos == p
67
67
  @curpos = p.clamp @cursor_top, lines
68
- buffer.mark_dirty
68
+ buffer.mark_dirty if buffer # not sure why the buffer is gone
69
69
  set_status
70
70
  end
71
71
 
@@ -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
- [(buffer.content_width.to_f * 0.2).to_i, MIN_FROM_WIDTH].max
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
- if m.list_unsubscribe && m.list_unsubscribe =~ /<mailto:(.*?)(\?subject=(.*?))?>/
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
- else
220
- BufferManager.flash "Can't find List-Unsubscribe header for this message."
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.filename)
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.filename)
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 = OrderedHash.new
851
- headers["Date"] = "#{m.date.to_message_nice_s} (#{m.date.to_nice_distance_s})"
852
- headers["Subject"] = m.subj
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?