2016-10-19 03:11:36 +00:00
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
2013-06-12 15:59:58 +00:00
2012-06-15 11:10:23 +00:00
# encoding: utf-8
2012-04-13 16:42:25 +00:00
require 'mail'
2012-12-05 01:38:30 +00:00
require 'encode'
2012-12-05 00:28:04 +00:00
2012-12-05 01:27:56 +00:00
class Channel :: EmailParser
2012-10-04 06:54:21 +00:00
= begin
2016-05-03 11:03:10 +00:00
parser = Channel :: EmailParser . new
mail = parser . parse ( msg_as_string )
2012-10-04 06:54:21 +00:00
mail = {
2015-08-30 18:16:29 +00:00
from : 'Some Name <some@example.com>' ,
from_email : 'some@example.com' ,
from_local : 'some' ,
from_domain : 'example.com' ,
from_display_name : 'Some Name' ,
message_id : 'some_message_id@example.com' ,
to : 'Some System <system@example.com>' ,
cc : 'Somebody <somebody@example.com>' ,
subject : 'some message subject' ,
body : 'some message body' ,
2016-06-21 15:14:15 +00:00
content_type : 'text/html' , # text/plain
2016-06-03 13:25:35 +00:00
date : Time . zone . now ,
2015-08-30 18:16:29 +00:00
attachments : [
2012-10-04 07:09:27 +00:00
{
2015-08-30 18:16:29 +00:00
data : 'binary of attachment' ,
filename : 'file_name_of_attachment.txt' ,
preferences : {
2017-03-06 11:18:04 +00:00
'content-alternative' = > true ,
'Mime-Type' = > 'text/plain' ,
'Charset: => ' iso - 8859 - 1 ' ,
2012-10-04 07:09:27 +00:00
} ,
} ,
2012-10-04 06:54:21 +00:00
] ,
2012-10-04 07:09:27 +00:00
# ignore email header
2015-08-30 18:16:29 +00:00
x - zammad - ignore : 'false' ,
2012-10-04 07:09:27 +00:00
# customer headers
2015-08-30 18:16:29 +00:00
x - zammad - customer - login : '' ,
x - zammad - customer - email : '' ,
x - zammad - customer - firstname : '' ,
x - zammad - customer - lastname : '' ,
2012-10-04 07:09:27 +00:00
2016-04-12 11:44:28 +00:00
# ticket headers (for new tickets)
2015-08-30 18:16:29 +00:00
x - zammad - ticket - group : 'some_group' ,
x - zammad - ticket - state : 'some_state' ,
x - zammad - ticket - priority : 'some_priority' ,
x - zammad - ticket - owner : 'some_owner_login' ,
2012-10-04 07:09:27 +00:00
2016-04-12 11:44:28 +00:00
# ticket headers (for existing tickets)
x - zammad - ticket - followup - group : 'some_group' ,
x - zammad - ticket - followup - state : 'some_state' ,
x - zammad - ticket - followup - priority : 'some_priority' ,
x - zammad - ticket - followup - owner : 'some_owner_login' ,
2012-10-04 07:09:27 +00:00
# article headers
2015-08-30 18:16:29 +00:00
x - zammad - article - internal : false ,
x - zammad - article - type : 'agent' ,
x - zammad - article - sender : 'customer' ,
2012-10-04 07:09:27 +00:00
# all other email headers
2015-08-30 18:16:29 +00:00
some - header : 'some_value' ,
2012-10-04 06:54:21 +00:00
}
= end
2015-12-14 09:23:14 +00:00
def parse ( msg )
2012-05-04 11:33:05 +00:00
data = { }
2015-12-14 09:23:14 +00:00
mail = Mail . new ( msg )
2012-05-04 11:33:05 +00:00
2012-05-05 09:24:05 +00:00
# set all headers
2017-03-15 15:13:48 +00:00
mail . header . fields . each { | field |
2017-03-14 11:00:38 +00:00
2015-05-07 09:04:40 +00:00
# full line, encode, ready for storage
2017-03-14 11:00:38 +00:00
begin
2017-03-15 15:13:48 +00:00
value = Encode . conv ( 'utf8' , field . to_s )
if value . blank?
value = field . raw_value
end
data [ field . name . to_s . downcase . to_sym ] = value
2017-03-14 11:00:38 +00:00
rescue = > e
2017-03-15 15:13:48 +00:00
data [ field . name . to_s . downcase . to_sym ] = field . raw_value
2017-03-14 11:00:38 +00:00
end
2015-05-07 09:04:40 +00:00
# if we need to access the lines by objects later again
2017-01-30 14:47:12 +00:00
data [ " raw- #{ field . name . downcase } " . to_sym ] = field
2012-05-05 09:24:05 +00:00
}
2017-03-15 15:13:48 +00:00
# verify content, ignore recipients with non email address
[ 'to' , 'cc' , 'delivered-to' , 'x-original-to' , 'envelope-to' ] . each { | field |
next if data [ field . to_sym ] . blank?
next if data [ field . to_sym ] =~ / @ /
data [ field . to_sym ] = ''
}
2017-08-15 07:46:21 +00:00
# get sender with @ / email address
2013-10-31 22:56:08 +00:00
from = nil
[ 'from' , 'reply-to' , 'return-path' ] . each { | item |
2017-03-15 15:13:48 +00:00
next if data [ item . to_sym ] . blank?
2017-08-15 07:46:21 +00:00
next if data [ item . to_sym ] !~ / @ /
2017-03-15 15:13:48 +00:00
from = data [ item . to_sym ]
2015-05-07 09:04:40 +00:00
break if from
2013-10-31 22:56:08 +00:00
}
2017-08-15 07:46:21 +00:00
# in case of no sender with email address - get sender
if ! from
[ 'from' , 'reply-to' , 'return-path' ] . each { | item |
next if data [ item . to_sym ] . blank?
from = data [ item . to_sym ]
break if from
}
end
2014-06-08 15:14:51 +00:00
# set x-any-recipient
data [ 'x-any-recipient' . to_sym ] = ''
[ 'to' , 'cc' , 'delivered-to' , 'x-original-to' , 'envelope-to' ] . each { | item |
2017-03-15 15:13:48 +00:00
next if data [ item . to_sym ] . blank?
2015-05-07 09:04:40 +00:00
if data [ 'x-any-recipient' . to_sym ] != ''
data [ 'x-any-recipient' . to_sym ] += ', '
2014-06-08 15:14:51 +00:00
end
2015-05-07 09:04:40 +00:00
data [ 'x-any-recipient' . to_sym ] += mail [ item . to_sym ] . to_s
2014-06-08 15:14:51 +00:00
}
2012-05-05 09:24:05 +00:00
# set extra headers
2017-05-26 13:34:32 +00:00
data = data . merge ( Channel :: EmailParser . sender_properties ( from ) )
2012-05-05 09:24:05 +00:00
2017-05-05 11:13:18 +00:00
# do extra encoding (see issue#1045)
if data [ :subject ] . present?
data [ :subject ] . sub! ( / ^= \ ?us-ascii \ ?Q \ ?(.+) \ ?=$ / , '\1' )
end
2012-05-05 09:24:05 +00:00
# compat headers
data [ :message_id ] = data [ 'message-id' . to_sym ]
2012-05-04 11:33:05 +00:00
# body
2013-06-12 15:59:58 +00:00
# plain_part = mail.multipart? ? (mail.text_part ? mail.text_part.body.decoded : nil) : mail.body.decoded
# html_part = message.html_part ? message.html_part.body.decoded : nil
2012-05-07 22:37:07 +00:00
data [ :attachments ] = [ ]
2012-10-05 06:42:12 +00:00
2012-07-02 18:52:27 +00:00
# multi part email
2012-05-04 19:30:22 +00:00
if mail . multipart?
2012-10-05 06:42:12 +00:00
2016-06-21 15:14:15 +00:00
# html attachment/body may exists and will be converted to strict html
if mail . html_part && mail . html_part . body
data [ :body ] = mail . html_part . body . to_s
data [ :body ] = Encode . conv ( mail . html_part . charset . to_s , data [ :body ] )
data [ :body ] = data [ :body ] . html2html_strict . to_s . force_encoding ( 'utf-8' )
if ! data [ :body ] . force_encoding ( 'UTF-8' ) . valid_encoding?
data [ :body ] = data [ :body ] . encode ( 'utf-8' , 'binary' , invalid : :replace , undef : :replace , replace : '?' )
end
data [ :content_type ] = 'text/html'
end
2012-07-02 18:52:27 +00:00
# text attachment/body exists
2016-06-21 15:14:15 +00:00
if data [ :body ] . empty? && mail . text_part
2012-10-04 06:54:21 +00:00
data [ :body ] = mail . text_part . body . decoded
2015-12-14 09:23:14 +00:00
data [ :body ] = Encode . conv ( mail . text_part . charset , data [ :body ] )
2017-06-23 11:57:24 +00:00
data [ :body ] = data [ :body ] . to_s . force_encoding ( 'utf-8' )
2012-10-05 06:42:12 +00:00
2014-08-06 11:41:10 +00:00
if ! data [ :body ] . valid_encoding?
2015-04-27 13:42:53 +00:00
data [ :body ] = data [ :body ] . encode ( 'utf-8' , 'binary' , invalid : :replace , undef : :replace , replace : '?' )
2014-08-06 11:41:10 +00:00
end
2016-06-21 15:14:15 +00:00
data [ :content_type ] = 'text/plain'
2015-11-26 08:29:58 +00:00
end
2014-08-06 11:41:10 +00:00
2016-06-21 15:14:15 +00:00
# any other attachments
if data [ :body ] . empty?
data [ :body ] = 'no visible content'
data [ :content_type ] = 'text/plain'
2012-07-02 18:52:27 +00:00
end
2012-06-15 11:10:23 +00:00
2012-07-02 18:52:27 +00:00
# add html attachment/body as real attachment
if mail . html_part
filename = 'message.html'
headers_store = {
'content-alternative' = > true ,
}
2012-06-15 11:10:23 +00:00
if mail . mime_type
headers_store [ 'Mime-Type' ] = mail . html_part . mime_type
end
if mail . charset
headers_store [ 'Charset' ] = mail . html_part . charset
end
attachment = {
2015-04-27 13:42:53 +00:00
data : mail . html_part . body . to_s ,
filename : mail . html_part . filename || filename ,
preferences : headers_store
2012-06-15 11:10:23 +00:00
}
data [ :attachments ] . push attachment
end
2012-10-04 06:54:21 +00:00
2012-07-02 18:52:27 +00:00
# get attachments
2013-01-23 13:47:57 +00:00
if mail . parts
mail . parts . each { | part |
2013-07-22 08:13:38 +00:00
# protect process to work fine with spam emails, see test/fixtures/mail15.box
begin
2015-12-14 09:23:14 +00:00
attachs = _get_attachment ( part , data [ :attachments ] , mail )
data [ :attachments ] . concat ( attachs )
2013-07-22 08:13:38 +00:00
rescue
2015-12-14 09:23:14 +00:00
attachs = _get_attachment ( part , data [ :attachments ] , mail )
data [ :attachments ] . concat ( attachs )
2012-07-02 18:52:27 +00:00
end
}
end
2014-12-28 23:11:37 +00:00
# not multipart email
2012-05-07 22:37:07 +00:00
2016-06-21 15:14:15 +00:00
# html part only, convert to text and add it as attachment
elsif mail . mime_type && mail . mime_type . to_s . casecmp ( 'text/html' ) . zero?
filename = 'message.html'
data [ :body ] = mail . body . decoded
data [ :body ] = Encode . conv ( mail . charset , data [ :body ] )
data [ :body ] = data [ :body ] . html2html_strict . to_s . force_encoding ( 'utf-8' )
if ! data [ :body ] . valid_encoding?
data [ :body ] = data [ :body ] . encode ( 'utf-8' , 'binary' , invalid : :replace , undef : :replace , replace : '?' )
end
data [ :content_type ] = 'text/html'
# add body as attachment
headers_store = {
'content-alternative' = > true ,
}
if mail . mime_type
headers_store [ 'Mime-Type' ] = mail . mime_type
end
if mail . charset
headers_store [ 'Charset' ] = mail . charset
end
attachment = {
data : mail . body . decoded ,
filename : mail . filename || filename ,
preferences : headers_store
}
data [ :attachments ] . push attachment
2016-01-15 17:22:57 +00:00
# text part only
2016-01-16 10:05:04 +00:00
elsif ! mail . mime_type || mail . mime_type . to_s == '' || mail . mime_type . to_s . casecmp ( 'text/plain' ) . zero?
2016-01-15 17:22:57 +00:00
data [ :body ] = mail . body . decoded
data [ :body ] = Encode . conv ( mail . charset , data [ :body ] )
if ! data [ :body ] . force_encoding ( 'UTF-8' ) . valid_encoding?
data [ :body ] = data [ :body ] . encode ( 'utf-8' , 'binary' , invalid : :replace , undef : :replace , replace : '?' )
end
2016-06-21 15:14:15 +00:00
data [ :content_type ] = 'text/plain'
2016-01-15 17:22:57 +00:00
else
filename = '-no name-'
2016-06-21 15:14:15 +00:00
data [ :body ] = 'no visible content'
2016-07-19 13:19:22 +00:00
data [ :content_type ] = 'text/plain'
2012-05-07 22:37:07 +00:00
2016-01-15 17:22:57 +00:00
# add body as attachment
headers_store = {
'content-alternative' = > true ,
}
if mail . mime_type
headers_store [ 'Mime-Type' ] = mail . mime_type
end
if mail . charset
headers_store [ 'Charset' ] = mail . charset
2012-05-07 22:37:07 +00:00
end
2016-01-15 17:22:57 +00:00
attachment = {
data : mail . body . decoded ,
filename : mail . filename || filename ,
preferences : headers_store
}
data [ :attachments ] . push attachment
2012-05-04 19:30:22 +00:00
end
2012-05-04 11:33:05 +00:00
2012-07-02 18:52:27 +00:00
# strip not wanted chars
2015-12-14 09:23:14 +00:00
data [ :body ] . gsub! ( / \ n \ r / , " \n " )
data [ :body ] . gsub! ( / \ r \ n / , " \n " )
2016-01-15 17:22:57 +00:00
data [ :body ] . tr! ( " \r " , " \n " )
2012-07-02 18:52:27 +00:00
2016-06-02 18:58:46 +00:00
# get mail date
begin
if mail . date
data [ :date ] = Time . zone . parse ( mail . date . to_s )
end
rescue
data [ :date ] = nil
end
2015-08-30 18:16:29 +00:00
# remember original mail instance
data [ :mail_instance ] = mail
2014-12-29 10:27:59 +00:00
data
2012-05-04 11:33:05 +00:00
end
2015-12-14 09:23:14 +00:00
def _get_attachment ( file , attachments , mail )
2013-01-23 13:47:57 +00:00
# check if sub parts are available
if ! file . parts . empty?
a = [ ]
2016-06-30 20:04:48 +00:00
file . parts . each { | p |
2015-12-14 09:23:14 +00:00
attachment = _get_attachment ( p , attachments , mail )
a . concat ( attachment )
2013-01-23 13:47:57 +00:00
}
return a
end
2014-12-28 23:11:37 +00:00
# ignore text/plain attachments - already shown in view
return [ ] if mail . text_part && mail . text_part . body . to_s == file . body . to_s
# ignore text/html - html part, already shown in view
return [ ] if mail . html_part && mail . html_part . body . to_s == file . body . to_s
2013-01-23 13:47:57 +00:00
# get file preferences
headers_store = { }
file . header . fields . each { | field |
2017-06-06 18:20:37 +00:00
# full line, encode, ready for storage
begin
value = Encode . conv ( 'utf8' , field . to_s )
if value . blank?
value = field . raw_value
end
headers_store [ field . name . to_s ] = value
rescue = > e
headers_store [ field . name . to_s ] = field . raw_value
end
2013-01-23 13:47:57 +00:00
}
# get filename from content-disposition
filename = nil
# workaround for: NoMethodError: undefined method `filename' for #<Mail::UnstructuredField:0x007ff109e80678>
begin
filename = file . header [ :content_disposition ] . filename
rescue
2016-01-28 18:55:15 +00:00
begin
2017-06-02 15:41:02 +00:00
if file . header [ :content_disposition ] . to_s =~ / filename="(.+?)" /i
filename = $1
elsif file . header [ :content_disposition ] . to_s =~ / filename='(.+?)' /i
filename = $1
elsif file . header [ :content_disposition ] . to_s =~ / filename=(.+?); /i
filename = $1
2016-01-28 18:55:15 +00:00
end
2016-01-28 19:08:13 +00:00
rescue
Rails . logger . debug 'Unable to get filename'
2013-01-23 13:47:57 +00:00
end
end
2017-06-06 18:20:37 +00:00
# as fallback, use raw values
if filename . blank?
if headers_store [ 'Content-Disposition' ] . to_s =~ / filename="(.+?)" /i
filename = $1
elsif headers_store [ 'Content-Disposition' ] . to_s =~ / filename='(.+?)' /i
filename = $1
elsif headers_store [ 'Content-Disposition' ] . to_s =~ / filename=(.+?); /i
filename = $1
end
end
2013-01-23 13:47:57 +00:00
# for some broken sm mail clients (X-MimeOLE: Produced By Microsoft Exchange V6.5)
2016-12-02 11:24:00 +00:00
filename || = file . header [ :content_location ] . to_s
2013-01-23 13:47:57 +00:00
# generate file name
2016-12-02 11:24:00 +00:00
if filename . blank?
2013-01-23 13:47:57 +00:00
attachment_count = 0
2016-06-30 20:04:48 +00:00
( 1 .. 1000 ) . each { | count |
2013-01-23 13:47:57 +00:00
filename_exists = false
filename = 'file-' + count . to_s
2016-06-30 20:04:48 +00:00
attachments . each { | attachment |
2013-01-23 13:47:57 +00:00
if attachment [ :filename ] == filename
filename_exists = true
end
}
break if filename_exists == false
}
end
# get mime type
if file . header [ :content_type ] && file . header [ :content_type ] . string
headers_store [ 'Mime-Type' ] = file . header [ :content_type ] . string
end
# get charset
if file . header && file . header . charset
headers_store [ 'Charset' ] = file . header . charset
end
# remove not needed header
headers_store . delete ( 'Content-Transfer-Encoding' )
headers_store . delete ( 'Content-Disposition' )
2017-03-14 05:39:58 +00:00
# cleanup content id, <> will be added automatically later
if headers_store [ 'Content-ID' ]
2017-04-26 12:41:30 +00:00
headers_store [ 'Content-ID' ] . gsub! ( / ^< / , '' )
headers_store [ 'Content-ID' ] . gsub! ( / >$ / , '' )
2017-03-14 05:39:58 +00:00
end
2017-04-04 21:17:55 +00:00
# workaround for mail gem
# https://github.com/zammad/zammad/issues/928
filename = Mail :: Encodings . value_decode ( filename )
2013-01-23 13:47:57 +00:00
attach = {
2015-04-27 13:42:53 +00:00
data : file . body . to_s ,
filename : filename ,
preferences : headers_store ,
2013-01-23 13:47:57 +00:00
}
2014-12-28 23:11:37 +00:00
[ attach ]
2013-01-23 13:47:57 +00:00
end
2015-08-30 18:16:29 +00:00
= begin
parser = Channel :: EmailParser . new
2017-04-26 14:50:57 +00:00
ticket , article , user , mail = parser . process ( channel , email_raw_string )
2015-08-30 18:16:29 +00:00
2016-11-07 22:20:58 +00:00
returns
2015-08-30 18:16:29 +00:00
2017-04-26 14:50:57 +00:00
[ ticket , article , user , mail ]
2015-08-30 18:16:29 +00:00
2016-11-07 22:20:58 +00:00
do not raise an exception - e . g . if used by scheduler
parser = Channel :: EmailParser . new
2017-04-26 14:50:57 +00:00
ticket , article , user , mail = parser . process ( channel , email_raw_string , false )
2016-11-07 22:20:58 +00:00
returns
2017-04-26 14:50:57 +00:00
[ ticket , article , user , mail ] || false
2016-11-07 22:20:58 +00:00
2015-08-30 18:16:29 +00:00
= end
2016-11-07 22:20:58 +00:00
def process ( channel , msg , exception = true )
2016-05-30 08:33:27 +00:00
_process ( channel , msg )
rescue = > e
# store unprocessable email for bug reporting
path = " #{ Rails . root } /tmp/unprocessable_mail/ "
FileUtils . mkpath path
md5 = Digest :: MD5 . hexdigest ( msg )
filename = " #{ path } / #{ md5 } .eml "
2016-11-03 22:18:10 +00:00
message = " ERROR: Can't process email, you will find it for bug reporting under #{ filename } , please create an issue at https://github.com/zammad/zammad/issues "
p message # rubocop:disable Rails/Output
p 'ERROR: ' + e . inspect # rubocop:disable Rails/Output
Rails . logger . error message
2017-04-19 10:09:54 +00:00
Rails . logger . error e
2016-05-30 08:33:27 +00:00
File . open ( filename , 'wb' ) { | file |
file . write msg
}
2016-11-07 22:20:58 +00:00
return false if exception == false
2016-08-23 09:49:12 +00:00
raise e . inspect + e . backtrace . inspect
2016-05-30 08:33:27 +00:00
end
def _process ( channel , msg )
2016-08-20 19:29:22 +00:00
# parse email
2015-12-14 09:23:14 +00:00
mail = parse ( msg )
2012-04-13 16:42:25 +00:00
2012-10-05 06:42:12 +00:00
# run postmaster pre filter
2016-04-12 15:21:48 +00:00
UserInfo . current_user_id = 1
2016-04-12 07:25:20 +00:00
filters = { }
2016-06-30 20:04:48 +00:00
Setting . where ( area : 'Postmaster::PreFilter' ) . order ( :name ) . each { | setting |
2016-04-12 07:25:20 +00:00
filters [ setting . name ] = Kernel . const_get ( Setting . get ( setting . name ) )
2012-10-05 06:42:12 +00:00
}
2016-06-30 20:04:48 +00:00
filters . each { | _prio , backend |
2016-06-02 18:58:46 +00:00
Rails . logger . debug " run postmaster pre filter #{ backend } "
2012-10-05 06:42:12 +00:00
begin
2015-08-30 18:16:29 +00:00
backend . run ( channel , mail )
2015-05-08 14:09:24 +00:00
rescue = > e
2015-05-04 18:58:28 +00:00
Rails . logger . error " can't run postmaster pre filter #{ backend } "
Rails . logger . error e . inspect
2015-08-31 09:27:12 +00:00
raise e
2012-10-05 06:42:12 +00:00
end
}
2012-10-04 07:09:27 +00:00
2012-05-06 20:48:23 +00:00
# check ignore header
2017-01-30 14:47:12 +00:00
if mail [ 'x-zammad-ignore' . to_sym ] == 'true' || mail [ 'x-zammad-ignore' . to_sym ] == true
2016-08-20 19:29:22 +00:00
Rails . logger . info " ignored email with msgid ' #{ mail [ :message_id ] } ' from ' #{ mail [ :from ] } ' because of x-zammad-ignore header "
2017-09-23 06:25:55 +00:00
return
2016-08-20 19:29:22 +00:00
end
# set interface handle
original_interface_handle = ApplicationHandleInfo . current
2012-05-06 20:48:23 +00:00
2016-08-23 10:54:29 +00:00
ticket = nil
article = nil
session_user = nil
2012-05-06 20:48:23 +00:00
2012-04-13 16:42:25 +00:00
# use transaction
2016-09-06 05:51:12 +00:00
Transaction . execute ( interface_handle : " #{ original_interface_handle } .postmaster " ) do
2012-04-13 16:42:25 +00:00
2016-08-23 09:49:12 +00:00
# get sender user
2017-01-30 14:47:12 +00:00
session_user_id = mail [ 'x-zammad-session-user-id' . to_sym ]
2016-08-23 09:49:12 +00:00
if ! session_user_id
raise 'No x-zammad-session-user-id, no sender set!'
2012-05-06 20:48:23 +00:00
end
2016-08-23 09:49:12 +00:00
session_user = User . lookup ( id : session_user_id )
if ! session_user
raise " No user found for x-zammad-session-user-id: #{ session_user_id } ! "
2012-04-13 16:42:25 +00:00
end
2012-10-04 07:09:27 +00:00
2012-04-13 16:42:25 +00:00
# set current user
2016-08-23 09:49:12 +00:00
UserInfo . current_user_id = session_user . id
2012-10-04 07:09:27 +00:00
2015-08-30 18:16:29 +00:00
# get ticket# based on email headers
2017-01-30 14:47:12 +00:00
if mail [ 'x-zammad-ticket-id' . to_sym ]
ticket = Ticket . find_by ( id : mail [ 'x-zammad-ticket-id' . to_sym ] )
2015-08-30 18:16:29 +00:00
end
2017-01-30 14:47:12 +00:00
if mail [ 'x-zammad-ticket-number' . to_sym ]
ticket = Ticket . find_by ( number : mail [ 'x-zammad-ticket-number' . to_sym ] )
2015-08-30 18:16:29 +00:00
end
2012-05-04 11:33:05 +00:00
2012-04-13 16:42:25 +00:00
# set ticket state to open if not new
if ticket
2016-04-12 11:44:28 +00:00
set_attributes_by_x_headers ( ticket , 'ticket' , mail , 'followup' )
2016-04-12 15:08:26 +00:00
# save changes set by x-zammad-ticket-followup-* headers
2017-09-23 06:25:55 +00:00
ticket . save! if ticket . has_changes_to_save?
2016-04-12 15:08:26 +00:00
2015-12-14 09:23:14 +00:00
state = Ticket :: State . find ( ticket . state_id )
state_type = Ticket :: StateType . find ( state . state_type_id )
2012-08-28 05:21:45 +00:00
2017-02-12 17:21:03 +00:00
# set ticket to open again or keep create state
2017-01-30 14:47:12 +00:00
if ! mail [ 'x-zammad-ticket-followup-state' . to_sym ] && ! mail [ 'x-zammad-ticket-followup-state_id' . to_sym ]
2017-02-12 17:21:03 +00:00
new_state = Ticket :: State . find_by ( default_create : true )
if ticket . state_id != new_state . id && ! mail [ 'x-zammad-out-of-office' . to_sym ]
ticket . state = Ticket :: State . find_by ( default_follow_up : true )
2016-08-23 09:49:12 +00:00
ticket . save!
2016-04-12 11:44:28 +00:00
end
2012-04-13 16:42:25 +00:00
end
end
# create new ticket
2012-05-06 20:48:23 +00:00
if ! ticket
2015-12-14 09:23:14 +00:00
preferences = { }
if channel [ :id ]
preferences = {
channel_id : channel [ :id ]
}
end
2016-05-23 17:23:06 +00:00
# get default group where ticket is created
group = nil
if channel [ :group_id ]
group = Group . lookup ( id : channel [ :group_id ] )
end
if ! group || group && ! group . active
group = Group . where ( active : true ) . order ( 'id ASC' ) . first
end
if ! group
group = Group . first
end
2017-02-13 14:05:28 +00:00
title = mail [ :subject ]
if title . blank?
title = '-'
end
2014-06-08 03:11:21 +00:00
ticket = Ticket . new (
2016-05-23 17:23:06 +00:00
group_id : group . id ,
2017-02-13 14:05:28 +00:00
title : title ,
2015-12-14 09:23:14 +00:00
preferences : preferences ,
2014-06-08 03:11:21 +00:00
)
2015-12-14 09:23:14 +00:00
set_attributes_by_x_headers ( ticket , 'ticket' , mail )
2012-05-06 20:48:23 +00:00
# create ticket
2016-08-20 19:29:22 +00:00
ticket . save!
2012-04-13 16:42:25 +00:00
end
2012-10-04 06:54:21 +00:00
2012-05-06 20:48:23 +00:00
# set attributes
2016-09-08 19:18:26 +00:00
ticket . with_lock do
article = Ticket :: Article . new (
ticket_id : ticket . id ,
type_id : Ticket :: Article :: Type . find_by ( name : 'email' ) . id ,
sender_id : Ticket :: Article :: Sender . find_by ( name : 'Customer' ) . id ,
content_type : mail [ :content_type ] ,
body : mail [ :body ] ,
from : mail [ :from ] ,
2017-04-26 14:50:57 +00:00
reply_to : mail [ :" reply-to " ] ,
2016-09-08 19:18:26 +00:00
to : mail [ :to ] ,
cc : mail [ :cc ] ,
subject : mail [ :subject ] ,
message_id : mail [ :message_id ] ,
internal : false ,
)
# x-headers lookup
set_attributes_by_x_headers ( article , 'article' , mail )
# create article
article . save!
# store mail plain
2016-12-20 23:07:47 +00:00
article . save_as_raw ( msg )
2016-09-08 19:18:26 +00:00
# store attachments
if mail [ :attachments ]
mail [ :attachments ] . each do | attachment |
Store . add (
object : 'Ticket::Article' ,
o_id : article . id ,
data : attachment [ :data ] ,
filename : attachment [ :filename ] ,
preferences : attachment [ :preferences ]
)
end
2012-04-13 16:42:25 +00:00
end
end
end
2012-10-05 06:42:12 +00:00
# run postmaster post filter
2016-04-21 18:49:30 +00:00
filters = { }
2016-06-30 20:04:48 +00:00
Setting . where ( area : 'Postmaster::PostFilter' ) . order ( :name ) . each { | setting |
2016-04-12 07:25:20 +00:00
filters [ setting . name ] = Kernel . const_get ( Setting . get ( setting . name ) )
2012-10-05 06:42:12 +00:00
}
2016-06-30 20:04:48 +00:00
filters . each { | _prio , backend |
2016-06-02 18:58:46 +00:00
Rails . logger . debug " run postmaster post filter #{ backend } "
2012-10-05 06:42:12 +00:00
begin
2016-08-23 10:54:29 +00:00
backend . run ( channel , mail , ticket , article , session_user )
2015-05-08 14:09:24 +00:00
rescue = > e
2015-05-04 18:58:28 +00:00
Rails . logger . error " can't run postmaster post filter #{ backend } "
Rails . logger . error e . inspect
2012-10-05 06:42:12 +00:00
end
}
2012-05-06 20:48:23 +00:00
# return new objects
2016-08-23 10:54:29 +00:00
[ ticket , article , session_user , mail ]
2012-04-13 16:42:25 +00:00
end
2012-11-07 16:38:09 +00:00
2016-12-20 23:07:47 +00:00
def self . check_attributes_by_x_headers ( header_name , value )
class_name = nil
attribute = nil
if header_name =~ / ^x-zammad-(.+?)-(followup-|)(.*)$ /i
class_name = $1
attribute = $3
end
return true if ! class_name
if class_name . downcase == 'article'
class_name = 'Ticket::Article'
end
return true if ! attribute
key_short = attribute [ attribute . length - 3 , attribute . length ]
return true if key_short != '_id'
class_object = Object . const_get ( class_name . to_classname )
return if ! class_object
class_instance = class_object . new
2017-01-31 17:13:45 +00:00
return false if ! class_instance . association_id_validation ( attribute , value )
2016-12-20 23:07:47 +00:00
true
end
2017-05-26 13:34:32 +00:00
def self . sender_properties ( from )
data = { }
2017-09-23 06:25:55 +00:00
return data if from . blank?
2017-05-26 13:34:32 +00:00
begin
2017-06-19 20:24:22 +00:00
list = Mail :: AddressList . new ( from )
list . addresses . each { | address |
data [ :from_email ] = address . address
data [ :from_local ] = address . local
data [ :from_domain ] = address . domain
data [ :from_display_name ] = address . display_name ||
( address . comments && address . comments [ 0 ] )
break if data [ :from_email ] . present? && data [ :from_email ] =~ / @ /
}
rescue = > e
if from =~ / <> / && from =~ / <.+?> /
data = sender_properties ( from . gsub ( / <> / , '' ) )
end
end
if data . empty? || data [ :from_email ] . blank?
2017-05-26 13:34:32 +00:00
from . strip!
if from =~ / ^(.+?)<(.+?)@(.+?)>$ /
data [ :from_email ] = " #{ $2 } @ #{ $3 } "
data [ :from_local ] = $2
data [ :from_domain ] = $3
data [ :from_display_name ] = $1
else
data [ :from_email ] = from
data [ :from_local ] = from
data [ :from_domain ] = from
end
end
# do extra decoding because we needed to use field.value
data [ :from_display_name ] = Mail :: Field . new ( 'X-From' , data [ :from_display_name ] ) . to_s
data [ :from_display_name ] . delete! ( '"' )
data [ :from_display_name ] . strip!
data [ :from_display_name ] . gsub! ( / ^' / , '' )
data [ :from_display_name ] . gsub! ( / '$ / , '' )
data
end
2016-04-12 11:44:28 +00:00
def set_attributes_by_x_headers ( item_object , header_name , mail , suffix = false )
2014-06-08 03:11:21 +00:00
# loop all x-zammad-hedaer-* headers
2016-06-30 20:04:48 +00:00
item_object . attributes . each { | key , _value |
2014-06-08 03:11:21 +00:00
# ignore read only attributes
next if key == 'updated_by_id'
next if key == 'created_by_id'
# check if id exists
2015-04-27 14:42:53 +00:00
key_short = key [ key . length - 3 , key . length ]
2014-06-08 03:11:21 +00:00
if key_short == '_id'
2015-04-27 14:41:03 +00:00
key_short = key [ 0 , key . length - 3 ]
2014-06-08 03:11:21 +00:00
header = " x-zammad- #{ header_name } - #{ key_short } "
2016-04-12 11:44:28 +00:00
if suffix
header = " x-zammad- #{ header_name } - #{ suffix } - #{ key_short } "
end
2016-12-20 23:07:47 +00:00
# only set value on _id if value/reference lookup exists
2014-06-08 03:11:21 +00:00
if mail [ header . to_sym ]
2017-01-30 14:47:12 +00:00
Rails . logger . info " header #{ header } found #{ mail [ header . to_sym ] } "
2014-06-08 03:11:21 +00:00
item_object . class . reflect_on_all_associations . map { | assoc |
2015-05-07 09:04:40 +00:00
next if assoc . name . to_s != key_short
2017-01-30 14:47:12 +00:00
Rails . logger . info " ASSOC found #{ assoc . class_name } lookup #{ mail [ header . to_sym ] } "
2015-05-07 09:04:40 +00:00
item = assoc . class_name . constantize
2016-12-20 23:07:47 +00:00
assoc_object = nil
assoc_has_object = false
2015-05-07 09:04:40 +00:00
if item . respond_to? ( :name )
2016-12-20 23:07:47 +00:00
assoc_has_object = true
2017-01-30 14:47:12 +00:00
if item . lookup ( name : mail [ header . to_sym ] )
assoc_object = item . lookup ( name : mail [ header . to_sym ] )
2015-05-07 09:04:40 +00:00
end
elsif item . respond_to? ( :login )
2016-12-20 23:07:47 +00:00
assoc_has_object = true
2017-01-30 14:47:12 +00:00
if item . lookup ( login : mail [ header . to_sym ] )
assoc_object = item . lookup ( login : mail [ header . to_sym ] )
2014-06-08 03:11:21 +00:00
end
end
2016-12-20 23:07:47 +00:00
next if assoc_has_object == false
if assoc_object
item_object [ key ] = assoc_object . id
next
end
# no assoc exists, remove header
mail . delete ( header . to_sym )
2014-06-08 03:11:21 +00:00
}
2012-10-05 06:42:12 +00:00
end
end
2014-06-08 03:11:21 +00:00
# check if attribute exists
header = " x-zammad- #{ header_name } - #{ key } "
2016-05-13 09:32:35 +00:00
if suffix
header = " x-zammad- #{ header_name } - #{ suffix } - #{ key } "
end
2017-01-30 14:47:12 +00:00
if mail [ header . to_sym ]
Rails . logger . info " header #{ header } found #{ mail [ header . to_sym ] } "
item_object [ key ] = mail [ header . to_sym ]
2014-06-08 03:11:21 +00:00
end
2012-10-05 06:42:12 +00:00
}
end
2016-08-20 19:29:22 +00:00
2012-07-25 13:58:08 +00:00
end
module Mail
2016-11-03 22:18:10 +00:00
2017-03-15 15:13:48 +00:00
# workaround to get content of no parseable headers - in most cases with non 7 bit ascii signs
class Field
def raw_value
value = Encode . conv ( 'utf8' , @raw_value )
2017-05-16 12:03:42 +00:00
return value if value . blank?
2017-03-15 15:13:48 +00:00
value . sub ( / ^.+?:( \ s|) / , '' )
end
end
2016-11-03 22:18:10 +00:00
# workaround to parse subjects with 2 different encodings correctly (e. g. quoted-printable see test/fixtures/mail9.box)
2012-07-25 13:58:08 +00:00
module Encodings
2015-05-05 11:01:16 +00:00
def self . value_decode ( str )
2012-07-25 13:58:08 +00:00
# Optimization: If there's no encoded-words in the string, just return it
2015-04-27 13:20:16 +00:00
return str unless str . index ( '=?' )
2012-07-25 13:58:08 +00:00
str = str . gsub ( / \ ?=( \ s*)= \ ? / , '?==?' ) # Remove whitespaces between 'encoded-word's
# Split on white-space boundaries with capture, so we capture the white-space as well
str . split ( / ([ \ t]) / ) . map do | text |
if text . index ( '=?' ) . nil?
text
else
# Join QP encoded-words that are adjacent to avoid decoding partial chars
2013-06-12 15:59:58 +00:00
# text.gsub!(/\?\=\=\?.+?\?[Qq]\?/m, '') if text =~ /\?==\?/
2012-07-25 13:58:08 +00:00
# Search for occurences of quoted strings or plain strings
text . scan ( / ( # Group around entire regex to include it in matches
2013-06-13 07:01:06 +00:00
\ = \ ?[ ^ ?] + \ ?( [ QB ] ) \ ?[ ^ ?] + ?\ ?\ = # Quoted String with subgroup for encoding method
| # or
. + ?( ?= \ = \ ?| $ ) # Plain String
2013-06-12 15:59:58 +00:00
) / xmi ) . map do | matches |
2012-07-25 13:58:08 +00:00
string , method = * matches
if method == 'b' || method == 'B'
b_value_decode ( string )
elsif method == 'q' || method == 'Q'
q_value_decode ( string )
else
string
end
end
end
2015-04-27 13:20:16 +00:00
end . join ( '' )
2012-07-25 13:58:08 +00:00
end
end
2016-11-03 22:18:10 +00:00
# issue#348 - IMAP mail fetching stops because of broken spam email (e. g. broken Content-Transfer-Encoding value see test/fixtures/mail43.box)
# https://github.com/zammad/zammad/issues/348
class Body
def decoded
if ! Encodings . defined? ( encoding )
#raise UnknownEncodingType, "Don't know how to decode #{encoding}, please call #encoded and decode it yourself."
Rails . logger . info " UnknownEncodingType: Don't know how to decode #{ encoding } ! "
raw_source
else
Encodings . get_encoding ( encoding ) . decode ( raw_source )
end
end
end
2015-04-27 14:15:29 +00:00
end