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.
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?