]> git.cworth.org Git - sup/commitdiff
mime-decode hook for plugging in external decoding programs
authorwmorgan <wmorgan@5c8cc53c-5e98-4d25-b20a-d8db53a31250>
Mon, 27 Aug 2007 18:18:33 +0000 (18:18 +0000)
committerwmorgan <wmorgan@5c8cc53c-5e98-4d25-b20a-d8db53a31250>
Mon, 27 Aug 2007 18:18:33 +0000 (18:18 +0000)
git-svn-id: svn://rubyforge.org/var/svn/sup/trunk@547 5c8cc53c-5e98-4d25-b20a-d8db53a31250

lib/sup/hook.rb
lib/sup/message.rb
lib/sup/util.rb

index 4c3f30e929f2a70209486816320c51241d57d0c6..8c4a2aee6fd54dc47628c8526f87467adfaa0a35 100644 (file)
@@ -19,7 +19,14 @@ class HookManager
     attr_writer :__locals
 
     def method_missing m, *a
-      @__locals[m] || super
+      case @__locals[m]
+      when Proc
+        @__locals[m].call(*a)
+      when nil
+        super
+      else
+        @__locals[m]
+      end
     end
 
     def say s
@@ -61,19 +68,17 @@ class HookManager
     hook = hook_for(name) or return
     context = @contexts[hook] ||= HookContext.new(name)
     context.__locals = locals
-    
+
+    result = nil
     begin
       result = context.instance_eval @hooks[name], fn_for(name)
-      if result.is_a? String
-        log "got return value: #{result.inspect}"
-        BufferManager.flash result 
-      end
     rescue Exception => e
       log "error running hook: #{e.message}"
       log e.backtrace.join("\n")
       BufferManager.flash "Error running hook: #{e.message}"
     end
     context.__cleanup
+    result
   end
 
   def register name, desc
@@ -106,6 +111,7 @@ private
             log "read '#{name}' from #{fn_for(name)}"
           end
         rescue SystemCallError => e
+          log "disabled hook for '#{name}': #{e.message}"
           nil
         end
     end
index d1aded0dee22aeaa6f30908d3cf04731fbf447c6..ffcde9e2e6ab020b5a32e8c830fa5c00c3d34327 100644 (file)
@@ -17,7 +17,18 @@ class Message
   SNIPPET_LEN = 80
   WRAP_LEN = 80 # wrap at this width
   RE_PATTERN = /^((re|re[\[\(]\d[\]\)]):\s*)+/i
-    
+
+  HookManager.register "mime-decode", <<EOS
+Executes when decoding a MIME attachment.
+Variables:
+  content_type: the context-type of the message
+      filename: the filename of the attachment as saved to disk (generated
+                on the fly, so don't call more than once)
+Return value:
+  The decoded text of the attachment, or nil if not decoded.
+EOS
+#' stupid ruby-mode
+
   ## some utility methods
   class << self
     def normalize_subj s; s.gsub(RE_PATTERN, ""); end
@@ -26,30 +37,46 @@ class Message
   end
 
   class Attachment
-    attr_reader :content_type, :filename, :content, :lines
-    def initialize content_type, filename, content
+    ## encoded_content is still possible MIME-encoded
+    ##
+    ## raw_content is after decoding but before being turned into
+    ## inlineable text.
+    ##
+    ## lines is array of inlineable text.
+
+    attr_reader :content_type, :filename, :lines, :raw_content
+
+    def initialize content_type, filename, encoded_content
       @content_type = content_type
       @filename = filename
-      @content = content
+      @raw_content = encoded_content.decode
+      charset = encoded_content.charset
 
-      if inlineable?
-        @lines = to_s.split("\n")
+      if @content_type =~ /^text\/plain\b/
+        @lines = Message.convert_from(@raw_content, charset).split("\n")
+      else
+        text = HookManager.run "mime-decode", :content_type => content_type,
+          :filename => lambda { write_to_disk }
+        @lines = text.split("\n") if text
       end
     end
 
-    def view!
-      file = Tempfile.new "redwood.attachment"
-      file.print raw_content
-      file.close
+    def inlineable?; !@lines.nil? end
 
-      system "/usr/bin/run-mailcap --action=view #{@content_type}:#{file.path} >& /dev/null"
+    def view!
+      path = write_to_disk
+      system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} >& /dev/null"
       $? == 0
     end
+    
+  private
 
-    def to_s; Message.decode_and_convert @content; end
-    def raw_content; @content.decode end
-
-    def inlineable?; @content_type =~ /^text\/plain/ end
+    def write_to_disk
+      file = Tempfile.new "redwood.attachment"
+      file.print @raw_content
+      file.close
+      file.path
+    end
   end
 
   class Text
@@ -283,10 +310,14 @@ private
   ## the general behavior i want is: ignore content-disposition, at
   ## least in so far as it suggests something being inline vs being an
   ## attachment. (because really, that should be the recipient's
-  ## decision to make.) if a mime part is text/plain, then decode it
-  ## and display it inline. if it has associated filename, then make
-  ## it collapsable and individually saveable; otherwise, treat it as
-  ## regular body text.
+  ## decision to make.) if a mime part is text/plain, OR if the user
+  ## decoding hook converts it, then decode it and display it
+  ## inline. for these decoded attachments, if it has associated
+  ## filename, then make it collapsable and individually saveable;
+  ## otherwise, treat it as regular body text.
+  ##
+  ## everything else is just an attachment and is not displayed
+  ## inline.
   ##
   ## so, in contrast to mutt, the user is not exposed to the workings
   ## of the gruesome slaughterhouse and sausage factory that is a
@@ -317,28 +348,22 @@ private
 
       ## otherwise, it's body text
       else
-        body = Message.decode_and_convert m
+        body = Message.convert_from m.body, m.charset
         text_to_chunks body.normalize_whitespace.split("\n")
       end
     end
   end
 
-  def self.decode_and_convert m
-    charset =
-      if m.header.field?("content-type") && m.header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/
-        $1
-      end
-
-    m.body && body = m.decode or raise MessageFormatError, "For some bizarre reason, RubyMail was unable to parse this message."
+  def self.convert_from body, charset
+    return body unless charset
 
-    if charset
-      begin
-        body = Iconv.iconv($encoding, charset, body).join
-      rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
-        Redwood::log "warning: error decoding message body from #{charset}: #{e.message}"
-      end
+    begin
+      Iconv.iconv($encoding, charset, body).join
+    rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
+      Redwood::log "warning: error (#{e.class.name}) decoding message body from #{charset}: #{e.message}"
+      File.open("sup-unable-to-decode.txt", "w") { |f| f.write body }
+      body
     end
-    body
   end
 
   ## parse the lines of text into chunk objects.  the heuristics here
index cf78dacd824de49a76730bed130204077b969535..6ddd758e8a9ef997fc4293b40f3c464a5d411c2b 100644 (file)
@@ -85,6 +85,12 @@ module RMail
 
       add_part a
     end
+
+    def charset
+      if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/
+        $1
+      end
+    end
   end
 end