lib/sup/message_chunks.rb in sup-0.13.2 vs lib/sup/message_chunks.rb in sup-0.13.2.1

- old
+ new

@@ -1,7 +1,8 @@ require 'tempfile' require 'rbconfig' +require 'shellwords' ## Here we define all the "chunks" that a message is parsed ## into. Chunks are used by ThreadViewMode to render a message. Chunks ## are used for both MIME stuff like attachments, for Sup's parsing of ## the message body into text, quote, and signature regions, and for @@ -57,10 +58,12 @@ module Redwood module Chunk class Attachment + ## please see note in write_to_disk on important usage + ## of quotes to avoid remote command injection. HookManager.register "mime-decode", <<EOS Decodes a MIME attachment into text form. The text will be displayed directly in Sup. For attachments that you wish to use a separate program to view (e.g. images), you should use the mime-view hook instead. @@ -73,10 +76,13 @@ the empty array. Return value: The decoded text of the attachment, or nil if not decoded. EOS + + ## please see note in write_to_disk on important usage + ## of quotes to avoid remote command injection. HookManager.register "mime-view", <<EOS Views a non-text MIME attachment. This hook allows you to run third-party programs for attachments that require such a thing (e.g. images). To instead display a text version of the attachment directly in Sup, use the mime-decode hook instead. @@ -98,12 +104,22 @@ ## raw_content is the post-MIME-decode content. this is used for ## saving the attachment to disk. attr_reader :content_type, :filename, :lines, :raw_content bool_reader :quotable + ## store tempfile objects as class variables so that they + ## are not removed when the viewing process returns. they + ## should be garbage collected when the class variable is removed. + @@view_tempfiles = [] + def initialize content_type, filename, encoded_content, sibling_types @content_type = content_type.downcase + if Shellwords.escape(@content_type) != @content_type + warn "content_type #{@content_type} is not safe, changed to application/octet-stream" + @content_type = 'application/octet-stream' + end + @filename = filename @quotable = false # changed to true if we can parse it through the # mime-decode hook, or if it's plain text @raw_content = if encoded_content.body @@ -114,11 +130,13 @@ text = case @content_type when /^text\/plain\b/ @raw_content else - HookManager.run "mime-decode", :content_type => content_type, + ## please see note in write_to_disk on important usage + ## of quotes to avoid remote command injection. + HookManager.run "mime-decode", :content_type => @content_type, :filename => lambda { write_to_disk }, :charset => encoded_content.charset, :sibling_types => sibling_types end @@ -145,33 +163,49 @@ def inlineable?; false end def expandable?; !viewable? end def initial_state; :open end def viewable?; @lines.nil? end def view_default! path + ## please see note in write_to_disk on important usage + ## of quotes to avoid remote command injection. case RbConfig::CONFIG['arch'] when /darwin/ - cmd = "open '#{path}'" + cmd = "open #{path}" else - cmd = "/usr/bin/run-mailcap --action=view '#{@content_type}:#{path}'" + cmd = "/usr/bin/run-mailcap --action=view #{@content_type}:#{path}" end debug "running: #{cmd.inspect}" BufferManager.shell_out(cmd) $? == 0 end def view! - path = write_to_disk - ret = HookManager.run "mime-view", :content_type => @content_type, - :filename => path - ret || view_default!(path) + ## please see note in write_to_disk on important usage + ## of quotes to avoid remote command injection. + write_to_disk do |file| + + @@view_tempfiles.push file # make sure the tempfile is not garbage collected before sup stops + + ret = HookManager.run "mime-view", :content_type => @content_type, + :filename => file.path + ret || view_default!(file.path) + end end + ## note that the path returned from write_to_disk is + ## Shellwords.escaped and is intended to be used without single + ## or double quotes. the use of either opens sup up for remote + ## code injection through the file name. def write_to_disk - file = Tempfile.new(["sup", @filename.gsub("/", "_") || "sup-attachment"]) - file.print @raw_content - file.close - file.path + begin + file = Tempfile.new(["sup", Shellwords.escape(@filename.gsub("/", "_")) || "sup-attachment"]) + file.print @raw_content + yield file if block_given? + return file.path + ensure + file.close + end end ## used when viewing the attachment as text def to_s @lines || @raw_content @@ -227,10 +261,10 @@ end class EnclosedMessage attr_reader :lines def initialize from, to, cc, date, subj - @from = from ? "unknown sender" : from.full_adress + @from = from ? "unknown sender" : from.full_address @to = to ? "" : to.map { |p| p.full_address }.join(", ") @cc = cc ? "" : cc.map { |p| p.full_address }.join(", ") if date @date = date.rfc822 else