require 'mail' require 'open3' require 'iconv' module Vmail class MessageFormatter # initialize with a Mail object def initialize(mail, uid = nil) @mail = mail @uid = uid end def list_parts(parts = (@mail.parts.empty? ? [@mail] : @mail.parts)) if parts.empty? return [] end lines = parts.map do |part| if part.multipart? list_parts(part.parts) else # part.charset could be used "- #{part.content_type}" end end lines.flatten end def process_body(target = @mail) out = if target.header['Content-Type'].to_s =~ /multipart\/mixed/ target.parts.map {|part| if part.multipart? part = find_text_or_html_part(target.parts) format_part(part) else format_part(part) end }.join("\n#{'-' * 39}\n") elsif target.header['Content-Type'].to_s =~ /multipart\/alternative/ part = find_text_or_html_part(target.parts) format_part(part) else format_part(target) end out end def format_part(part) if part && part.respond_to?(:header) case part.header["Content-Type"].to_s when /text\/html/ format_html_body(part) when /text\/plain/ format_text_body(part) when /message\/rfc/ m = Mail.new(part.body.decoded) process_body(m) else # just format_text on it anyway format_text_body(part) end else "[NO BODY]" end rescue puts $! "[error:] #{$!}" end def find_text_or_html_part(parts = @mail.parts) if parts.empty? return @mail end part = parts.detect {|part| part.multipart?} if part find_text_or_html_part(part.parts) else # no multipart part part = parts.detect {|part| (part.header["Content-Type"].to_s =~ /text\/plain/) } if part return part else parts.first end end end def format_text_body(part) text = part.body.decoded.gsub("\r", '') charset = (part.content_type_parameters && part.content_type_parameters['charset']) || encoding if (charset && charset != 'UTF-8') || (text.encoding != 'UTF-8') Iconv.conv('UTF-8//TRANSLIT//IGNORE', charset, text) else text end end # depend on lynx or whatever is set by the VMAIL_HTML_PART_READER # variable def format_html_body(part) html_tool = ENV['VMAIL_HTML_PART_READER'] || 'lynx -stdin -dump' html = part.body.decoded.gsub("\r", '') stdin, stdout, stderr = Open3.popen3(html_tool) stdin.puts html stdin.close output = "[vmail: html part translated into plaintext by '#{html_tool}']\n\n" + stdout.read charset = part.content_type_parameters && part.content_type_parameters['charset'] if charset && charset != 'UTF-8' Iconv.conv('UTF-8//TRANSLIT//IGNORE', charset, output) else output end end def extract_headers(mail = @mail) headers = {'from' => utf8(mail['from'].decoded), 'date' => (mail.date.strftime('%a, %b %d %I:%M %p %Z %Y') rescue mail.date), 'to' => mail['to'].nil? ? nil : utf8(mail['to'].decoded), 'subject' => utf8(mail.subject) } if !mail.cc.nil? headers['cc'] = utf8(mail['cc'].decoded.to_s) end if !mail.reply_to.nil? headers['reply_to'] = utf8(mail['reply_to'].decoded) end headers rescue {'error' => $!} end def encoding @encoding ||= @mail.header.charset || 'UTF-8' end def utf8(string, this_encoding = encoding) return '' unless string out = if this_encoding && this_encoding.upcase != 'UTF-8' Iconv.conv('UTF-8//TRANSLIT/IGNORE', this_encoding, string) elsif this_encoding.upcase == 'UTF-8' string else # assume UTF-8 Iconv.conv('US-ASCII//TRANSLIT/IGNORE', 'UTF-8', string) end out rescue "[error: #$!]" end end end