]> git.cworth.org Git - sup/blobdiff - lib/sup/message.rb
Merge branch 'better-buffer-list' into next
[sup] / lib / sup / message.rb
index 853f2fccb3676d4df05759d4b3e81e19df04efd4..0ee46fb25d7ecbb97807910c9fa0785337bccc2c 100644 (file)
@@ -37,7 +37,7 @@ class Message
   DEFAULT_SENDER = "(missing sender)"
 
   attr_reader :id, :date, :from, :subj, :refs, :replytos, :to, :source,
-              :cc, :bcc, :labels, :list_address, :recipient_email, :replyto,
+              :cc, :bcc, :labels, :attachments, :list_address, :recipient_email, :replyto,
               :source_info, :list_subscribe, :list_unsubscribe
 
   bool_reader :dirty, :source_marked_read, :snippet_contains_encrypted_content
@@ -54,6 +54,7 @@ class Message
     @dirty = false
     @encrypted = false
     @chunks = nil
+    @attachments = []
 
     ## we need to initialize this. see comments in parse_header as to
     ## why.
@@ -63,7 +64,7 @@ class Message
   end
 
   def parse_header header
-    header.each { |k, v| header[k.downcase] = v }
+    header.keys.each { |k| header[k.downcase] = header[k] } # canonicalize
 
     fakeid = nil
     fakename = nil
@@ -77,10 +78,10 @@ class Message
     
     @from =
       if header["from"]
-        PersonManager.person_for header["from"]
+        Person.from_address header["from"]
       else
         fakename = "Sup Auto-generated Fake Sender <sup@fake.sender.example.com>"
-        PersonManager.person_for fakename
+        Person.from_address fakename
       end
 
     Redwood::log "faking message-id for message from #@from: #{id}" if fakeid
@@ -95,7 +96,8 @@ class Message
         begin
           Time.parse date
         rescue ArgumentError => e
-          raise MessageFormatError, "unparsable date #{header['date']}: #{e.message}"
+          Redwood::log "faking date header for #{@id} due to error parsing date #{header['date'].inspect}: #{e.message}"
+          Time.now
         end
       else
         Redwood::log "faking date header for #{@id}"
@@ -103,9 +105,9 @@ class Message
       end
 
     @subj = header.member?("subject") ? header["subject"].gsub(/\s+/, " ").gsub(/\s+$/, "") : DEFAULT_SUBJECT
-    @to = PersonManager.people_for header["to"]
-    @cc = PersonManager.people_for header["cc"]
-    @bcc = PersonManager.people_for header["bcc"]
+    @to = Person.from_address_list header["to"]
+    @cc = Person.from_address_list header["cc"]
+    @bcc = Person.from_address_list header["bcc"]
 
     ## before loading our full header from the source, we can actually
     ## have some extra refs set by the UI. (this happens when the user
@@ -115,10 +117,10 @@ class Message
     @refs = (@refs + refs).uniq
     @replytos = (header["in-reply-to"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
 
-    @replyto = PersonManager.person_for header["reply-to"]
+    @replyto = Person.from_address header["reply-to"]
     @list_address =
       if header["list-post"]
-        @list_address = PersonManager.person_for header["list-post"].gsub(/^<mailto:|>$/, "")
+        @list_address = Person.from_address header["list-post"].gsub(/^<mailto:|>$/, "")
       else
         nil
       end
@@ -135,6 +137,10 @@ class Message
     @dirty = true
   end
 
+  def remove_ref ref
+    @dirty = true if @refs.delete ref
+  end
+
   def snippet; @snippet || (chunks && @snippet); end
   def is_list_message?; !@list_address.nil?; end
   def is_draft?; @source.is_a? DraftLoader; end
@@ -143,11 +149,24 @@ class Message
     @source.fn_for_offset @source_info
   end
 
-  def sanitize_message_id mid; mid.gsub(/\s/, "") end
+  ## sanitize message ids by removing spaces and non-ascii characters.
+  ## also, truncate to 255 characters. all these steps are necessary
+  ## to make ferret happy. of course, we probably fuck up a couple
+  ## valid message ids as well. as long as we're consistent, this
+  ## should be fine, though.
+  ##
+  ## also, mostly the message ids that are changed by this belong to
+  ## spam email.
+  ##
+  ## an alternative would be to SHA1 or MD5 all message ids on a regular basis.
+  ## don't tempt me.
+  def sanitize_message_id mid; mid.gsub(/(\s|[^\000-\177])+/, "")[0..254] end
 
   def save index
-    index.sync_message self if @dirty
+    return unless @dirty
+    index.sync_message self
     @dirty = false
+    true
   end
 
   def has_label? t; @labels.member? t; end
@@ -248,13 +267,14 @@ EOS
     with_source_errors_handled { @source.each_raw_message_line(@source_info, &b) }
   end
 
-  def content
+  ## returns all the content from a message that will be indexed
+  def indexable_content
     load_from_source!
     [
-      from && "#{from.name} #{from.email}",
-      to.map { |p| "#{p.name} #{p.email}" },
-      cc.map { |p| "#{p.name} #{p.email}" },
-      bcc.map { |p| "#{p.name} #{p.email}" },
+      from && from.indexable_content,
+      to.map { |p| p.indexable_content },
+      cc.map { |p| p.indexable_content },
+      bcc.map { |p| p.indexable_content },
       chunks.select { |c| c.is_a? Chunk::Text }.map { |c| c.lines },
       Message.normalize_subj(subj),
     ].flatten.compact.join " "
@@ -371,8 +391,9 @@ private
     elsif m.header.content_type == "message/rfc822"
       payload = RMail::Parser.read(m.body)
       from = payload.header.from.first
-      from_person = from ? PersonManager.person_for(from.format) : nil
-      [Chunk::EnclosedMessage.new(from_person, payload.to_s)]
+      from_person = from ? Person.from_address(from.format) : nil
+      [Chunk::EnclosedMessage.new(from_person, payload.to_s)] +
+        message_to_chunks(payload, encrypted)
     else
       filename =
         ## first, paw through the headers looking for a filename
@@ -397,25 +418,29 @@ private
 
       ## if there's a filename, we'll treat it as an attachment.
       if filename
+        # add this to the attachments list if its not a generated html
+        # attachment (should we allow images with generated names?).
+        # Lowercase the filename because searches are easier that way 
+        @attachments.push filename.downcase unless filename =~ /^sup-attachment-/
+        add_label :attachment unless filename =~ /^sup-attachment-/
         [Chunk::Attachment.new(m.header.content_type, filename, m, sibling_types)]
 
       ## otherwise, it's body text
       else
         body = Message.convert_from m.decode, m.charset if m.body
-        text_to_chunks (body || "").normalize_whitespace.split("\n"), encrypted
+        text_to_chunks((body || "").normalize_whitespace.split("\n"), encrypted)
       end
     end
   end
 
   def self.convert_from body, charset
-    charset = "utf-8" if charset =~ /UTF_?8/i
     begin
       raise MessageFormatError, "RubyMail decode returned a null body" unless body
       return body unless charset
-      Iconv.iconv($encoding + "//IGNORE", charset, body + " ").join[0 .. -2]
+      Iconv.easy_decode($encoding, charset, body)
     rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence, MessageFormatError => 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 }
+      File.open(File.join(BASE_DIR,"unable-to-decode.txt"), "w") { |f| f.write body }
       body
     end
   end