# # Receive a mail from which an elt is created. # # An associated mail is kept to make sure we don't lose any data. Attachments # are also managed in an associated table # class Mail < ActiveRecord::Base usesguid belongs_to :elt # # Receive this new elt as an email, sent through app/helpers/mailman.rb # # TODO # Loop detection, will need more work # def Mail.receive(mail) logger.info "Receive \"#{mail.subject}\"" if Mail.find_by_message(mail.message_id) \ or (mail['X-Message-Id'] \ and Mail.find_by_message(mail['X-Message-Id'].to_s)) logger.info "Already received (id: #{mail.message_id})...\n" else Mail.new.receive mail end end """ _ _ __ ___ ___ ___(_)_ _____ | '__/ _ \/ __/ _ \ \ \ / / _ \ | | | __/ (_| __/ |\ V / __/ |_| \___|\___\___|_| \_/ \___| """ # # Receive a mail that will be stored as an elt. # # The algorithm to define its parent is simple: # - header "references" # - or any subject between [] # - the "to" part before @#{ActionMailer::Base.server_settings[:domain]} # - in a "mail/lost+found" thread # def receive(mail) logger.info "Receive mail #{mail.message_id}" build_elt :created_on => mail.date, :subject => unquote(mail.subject), :body => '', :mail => self # Try to find its mail parent in the db if mail.in_reply_to and Mail.find_by_message mail.in_reply_to elt.parent_id = mail.in_reply_to end # Try to find its mail parent in the db if not elt.parent and mail.references for parent in mail.references m = Mail.find_by_message parent.to_s elt.parent = m.elt if m end end begin # No reference matching an existing parent, let's try a mailing list we # deduce from an eventual [subject] if not elt.parent and elt.subject.match(/\[[\w-]*\]/) parentId = elt.subject.match(/\[[\w-]*\]/) parentId = parentId[0].gsub(/[\[\]]/, '') if parentId elt.parent = Elt.find parentId end # No reference matching an existing parent, let's try the "to" field to = mail["Envelope-to"].to_s + ', ' + mail.to.to_s if not elt.parent and to.match(/[\w-]*@#{ActionMailer::Base.server_settings[:domain]}/) parentId = to.match(/[\w-]*@#{ActionMailer::Base.server_settings[:domain]}/)[0] \ .gsub(/@#{ActionMailer::Base.server_settings[:domain]}/, '') elt.parent = Elt.find parentId end if not elt.parent parentId = "lost+found" elt.parent = Elt.find parentId end rescue ActiveRecord::RecordNotFound elt.build_parent :parent_id => 'mail', :subject => parentId, :body => '' elt.parent.id = parentId elt.parent.publish elt.save elt.parent.parent.add_child elt.parent end mngAttachment mail if mail elt.person = Person.find_by_email(mail.from) \ || Person.find_by_name(unquote(mail.friendly_from)) \ || elt.build_person(:id => unquote(mail.friendly_from).gsub(/\s/, '_'), :name => unquote(mail.friendly_from), :email => mail.from.first) self.mail_parents = mail.references self.message = mail.message_id self.file = mail.encoded elt.publish if mail.to.to_s.match(/people@#{ActionMailer::Base.server_settings[:domain]}/) elt.person.image = "/attachment/file/#{elt.attachments.first.file_relative_path}" elt.person.save end elt.parent.add_child elt self end """ _ _ _ _ _ __ _ _| |__ | (_)___| |__ | '_ \| | | | '_ \| | / __| '_ \ | |_) | |_| | |_) | | \__ \ | | | | .__/ \__,_|_.__/|_|_|___/_| |_| |_| """ # # An elt needs to be published as a mail # def publish logger.info "Publish mail" if message and not message.blank? and file mail = TMail::Mail.parse file else mail = MailNotify.create_publish elt self.mail_parents = mail.references self.file = mail.encoded end mail['Precedence'] = 'list' mail['X-Loop'] = ActionMailer::Base.server_settings[:domain] mail['List-Archive'] = "http://"+ActionMailer::Base.server_settings[:domain] mail['Errors-To'] = "errors@"+ActionMailer::Base.server_settings[:domain] mail.bcc = elt.all_recipients \ .select { |i| i.email and not i.email.blank? } \ .collect { |i| i.email } \ .uniq \ .join(', ') # # Redefine the recipients used when delivering the mail to the local smtp server # # In fact, only send the mail to the bcc recipients, the to and/or cc are # just here for information # def mail.destinations(default = nil) ret = [] %w( bcc ).each do |nm| if h = @header[nm] h.addrs.each {|i| ret.push i.address } end end ret.empty? ? default : ret end if mail.message_id # Let's not change the message id def mail.add_message_id end end # Added to make sure it is not lost, but not modified if already existant mail['X-Message-Id'] = mail.message_id if not mail['X-Message-Id'] MailNotify::deliver(mail) if mail.destinations and not mail.destinations.empty? # Do it after sending, to get the actual message id as generated by the MTA # Note that the id of a resent mail is regenerated, but we don't record it, # we record the initial id self.message = mail.message_id if not self.message or self.message.blank? logger.info "Published with id \"#{message}\"" end private # This is to make sure we only have utf-8, no iso-8859 def unquote(text) text.gsub(/\=\?.*?\?\=/) { |m| TMail::Unquoter.unquote_and_convert_to m, 'utf-8' } end # Get and store the attachments def mngAttachment(attachment) if attachment.multipart? attachment.parts.each { |part| mngAttachment part } elsif attachment.disposition_param 'filename' File.open("/tmp/#{attachment.disposition_param('filename')}", 'w') { |file| file << attachment.body elt.attachments.build :file => file, :content_type => attachment.content_type } elsif attachment.type_param 'name' File.open("/tmp/#{attachment.type_param('name')}", 'w') { |file| file << attachment.body elt.attachments.build :file => file, :content_type => attachment.content_type } elsif (attachment.content_type and attachment.content_type.match(/text\/plain/)) \ or (!attachment.content_type and attachment.parts.size == 0) charset = attachment.type_param 'charset' if charset and !charset.empty? if attachment.transfer_encoding == 'quoted-printable' # Here to correct a null character which can occur in some mails # It looks like ==0 or ^@ !!! # Otherwise the elt can not be saved in the db :( elt.body += Iconv.new(charset, 'utf-8').iconv(attachment.body).gsub(/\0/, '') else elt.body += Iconv.new(charset, 'utf-8').iconv(attachment.body) end else # Here too have to remove any eventual null character! elt.body += attachment.body.gsub(/\0/, '') end end end end