diff --git a/include/class.format.php b/include/class.format.php index 3377b4fe4938ac9d1be98c3ad592bab39bdd3647..e3f7b415a7553f6f77e6da85b31aff4d2c2f9ba5 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -202,7 +202,7 @@ class Format { function safe_html($html) { // Remove HEAD and STYLE sections $html = preg_replace( - array(':<(head|style|script).+</\1>:is', # <head> and <style> sections + array(':<(head|style|script).+?</\1>:is', # <head> and <style> sections ':<!\[[^]<]+\]>:', # <![if !mso]> and friends ':<!DOCTYPE[^>]+>:', # <!DOCTYPE ... > ':<\?[^>]+>:', # <?xml version="1.0" ... > diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index 510de3e8354c64e767a860bfea11bb038891f10b..f1ea77d74ac9979bdffccfd195ca76eabd98c3a9 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -617,9 +617,11 @@ class MailFetcher { // Fetch deliver status report $vars['message'] = $this->getDeliveryStatusMessage($mid); $vars['thread-type'] = 'N'; + $vars['flags']['bounce'] = true; } else { $vars['message'] = $this->getBody($mid); + $vars['flags']['bounce'] = TicketFilter::isBounce($info); } diff --git a/include/class.mailparse.php b/include/class.mailparse.php index 33999522ed0a63e307b46aeec9356388a15b0637..92b275bbec2efa9fc2cc571ccc0e12bb1edbe011 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -584,12 +584,14 @@ class EmailDataParser { // Fetch deliver status report $data['message'] = $parser->getDeliveryStatusMessage(); $data['thread-type'] = 'N'; + $data['flags']['bounce'] = true; } else { // Typical email $data['message'] = $parser->getBody(); $data['in-reply-to'] = @$parser->struct->headers['in-reply-to']; $data['references'] = @$parser->struct->headers['references']; + $data['flags']['bounce'] = TicketFilter::isBounce($data['header']); } $data['subject'] = $parser->getSubject(); diff --git a/include/class.thread.php b/include/class.thread.php index b4c10305d09372d91c28ed74094840aa1b0eee14..0722c80d2027ca5691afe4dc37e805911f86658b 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -246,7 +246,7 @@ Class ThreadEntry { if(!$id && !($id=$this->getId())) return false; - $sql='SELECT thread.*, info.email_mid ' + $sql='SELECT thread.*, info.email_mid, info.headers ' .' ,count(DISTINCT attach.attach_id) as attachments ' .' FROM '.TICKET_THREAD_TABLE.' thread ' .' LEFT JOIN '.TICKET_EMAIL_INFO_TABLE.' info @@ -338,18 +338,18 @@ Class ThreadEntry { return $this->ht['email_mid']; } - function getEmailHeaders() { + function getEmailHeaderArray() { require_once(INCLUDE_DIR.'class.mailparse.php'); - $sql = 'SELECT headers FROM '.TICKET_EMAIL_INFO_TABLE - .' WHERE thread_id='.$this->getId(); - $headers = db_result(db_query($sql)); - return Mail_Parse::splitHeaders($headers); + if (!isset($this->ht['@headers'])) + $this->ht['@headers'] = Mail_Parse::splitHeaders($this->ht['headers']); + + return $this->ht['@headers']; } function getEmailReferences() { if (!isset($this->_references)) { - $headers = self::getEmailHeaders(); + $headers = self::getEmailHeaderArray(); if (isset($headers['References']) && $headers['References']) $this->_references = $headers['References']." "; $this->_references .= $this->getEmailMessageId(); @@ -427,8 +427,8 @@ Class ThreadEntry { function isAutoReply() { if (!isset($this->is_autoreply)) - $this->is_autoreply = $this->getEmailHeader() - ? TicketFilter::isAutoReply($this->getEmailHeader()) : false; + $this->is_autoreply = $this->getEmailHeaderArray() + ? TicketFilter::isAutoReply($this->getEmailHeaderArray()) : false; return $this->is_autoreply; } @@ -436,8 +436,8 @@ Class ThreadEntry { function isBounce() { if (!isset($this->is_bounce)) - $this->is_bounce = $this->getEmailHeader() - ? TicketFilter::isBounce($this->getEmailHeader()) : false; + $this->is_bounce = $this->getEmailHeaderArray() + ? TicketFilter::isBounce($this->getEmailHeaderArray()) : false; return $this->is_bounce; } @@ -523,10 +523,10 @@ Class ThreadEntry { return 0; // TODO: Add a unique index to TICKET_ATTACHMENT_TABLE (file_id, - // ticket_id), and remove this block + // ref_id), and remove this block if ($id = db_result(db_query('SELECT attach_id FROM '.TICKET_ATTACHMENT_TABLE - .' WHERE file_id='.db_input($fileId).' AND ticket_id=' - .db_input($this->getTicketId())))) + .' WHERE file_id='.db_input($fileId).' AND ref_id=' + .db_input($this->getId())))) return $id; $sql ='INSERT IGNORE INTO '.TICKET_ATTACHMENT_TABLE.' SET created=NOW() ' diff --git a/include/class.ticket.php b/include/class.ticket.php index 57d8ff42bdcf5a63268dfff5eda0c1ae2b4f753d..27908b5fe2ecb7a65d86282b421cb2d082e2e68d 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1577,9 +1577,10 @@ class Ticket { if(!$alerts) return $message; //Our work is done... - $autorespond = true; - if ($autorespond && $message->isBounceOrAutoReply()) - $autorespond=false; + // Do not auto-respond to bounces and other auto-replies + $autorespond = isset($vars['flags']) ? !$vars['flags']['bounce'] : true; + if ($autorespond && $message->isAutoReply()) + $autorespond = false; $this->onMessage($message, $autorespond); //must be called b4 sending alerts to staff. @@ -1813,6 +1814,10 @@ class Ticket { if(!($note=$this->getThread()->addNote($vars, $errors))) return null; + if (isset($vars['flags']) && $vars['flags']['bounce']) + // No alerts for bounce emails + $alert = false; + //Set state: Error on state change not critical! if(isset($vars['state']) && $vars['state']) { if($this->setState($vars['state'])) @@ -2391,6 +2396,8 @@ class Ticket { # Messages that are clearly auto-responses from email systems should # not have a return 'ping' message + if (isset($vars['flags']) && $vars['flags']['bounce']) + $autorespond = false; if ($autorespond && $message->isAutoReply()) $autorespond = false; @@ -2455,6 +2462,11 @@ class Ticket { // post response - if any $response = null; if($vars['response'] && $thisstaff->canPostReply()) { + + // unpack any uploaded files into vars. + if ($_FILES['attachments']) + $vars['files'] = AttachmentFile::format($_FILES['attachments']); + $vars['response'] = $ticket->replaceVars($vars['response']); if(($response=$ticket->postReply($vars, $errors, false))) { //Only state supported is closed on response diff --git a/include/class.validator.php b/include/class.validator.php index a09f9738ac22fdb384e91346cdd2fe08956e40bc..bb6745e7fdbee59f07bd8788018463befc486c2a 100644 --- a/include/class.validator.php +++ b/include/class.validator.php @@ -141,7 +141,8 @@ class Validator { /*** Functions below can be called directly without class instance. Validator::func(var..); (nolint) ***/ function is_email($email) { - return preg_match('/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i',$email); + require_once PEAR_DIR.'Mail/RFC822.php'; + return !PEAR::isError(Mail_RFC822::parseAddressList($email)); } function is_phone($phone) { /* We're not really validating the phone number but just making sure it doesn't contain illegal chars and of acceptable len */ diff --git a/include/upgrader/streams/core/f8856d56-abe9c0cb.patch.sql b/include/upgrader/streams/core/f8856d56-abe9c0cb.patch.sql index 09b593a16a1abe2e546ec88ede4bf0ceb1caff9c..30a00eb8ca45bbfd7ca844e3e27f3e444c4962de 100644 --- a/include/upgrader/streams/core/f8856d56-abe9c0cb.patch.sql +++ b/include/upgrader/streams/core/f8856d56-abe9c0cb.patch.sql @@ -93,6 +93,11 @@ INSERT INTO `%TABLE_PREFIX%ticket_email_info` FROM `%TABLE_PREFIX%ticket_message` WHERE `messageId` IS NOT NULL AND `messageId` <>''; +-- Change collation to utf8_general_ci - to avoid Illegal mix of collations error +ALTER TABLE `%TABLE_PREFIX%ticket_attachment` + CHANGE `ref_type` `ref_type` ENUM('M','R','N') CHARACTER + SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'M'; + -- Update attachment table UPDATE `%TABLE_PREFIX%ticket_attachment` SET `ref_id` = ( SELECT T2.`id` FROM `%TABLE_PREFIX%ticket_thread` T2 diff --git a/scp/tickets.php b/scp/tickets.php index 901e4a9402bc47f17a58f8dee9386a90683abace..f679f5035a2355ef80482f4a8084f09975c25427 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -484,9 +484,6 @@ if($_POST && !$errors): $vars = $_POST; $vars['uid'] = $user? $user->getId() : 0; - if($_FILES['attachments']) - $vars['files'] = AttachmentFile::format($_FILES['attachments']); - if(($ticket=Ticket::open($vars, $errors))) { $msg='Ticket created successfully'; $_REQUEST['a']=null; diff --git a/setup/test/tests/test.validation.php b/setup/test/tests/test.validation.php index 942767329642101c1f6db4a483ff23798936c118..20b1eae9aa85cf36a9355de41c468feb74e5e6a1 100644 --- a/setup/test/tests/test.validation.php +++ b/setup/test/tests/test.validation.php @@ -15,6 +15,39 @@ class TestValidation extends Test { $this->assert(Validator::is_username('ä¸å›½æœŸåˆŠå…¨æ–‡æ•°æ®')); // Non-letters $this->assert(!Validator::is_username('j®red')); + // Special chars + $this->assert(Validator::is_username('jar.ed')); + $this->assert(Validator::is_username('jar_ed')); + $this->assert(Validator::is_username('jar-ed')); + // Illegals + $this->assert(!Validator::is_username('j red')); + $this->assert(!Validator::is_username('jared ')); + $this->assert(!Validator::is_username(' jared')); + } + + function testValidEmail() { + // Common emails + $this->assert(Validator::is_email('jared@domain.tld')); + $this->assert(Validator::is_email('jared12@domain.tld')); + $this->assert(Validator::is_email('jared.12@domain.tld')); + $this->assert(Validator::is_email('jared_12@domain.tld')); + $this->assert(Validator::is_email('jared-12@domain.tld')); + + // Very likely illegal + $this->assert(!Validator::is_email('jared r@domain.tld')); + $this->assert(Validator::is_email('jared@host')); + + // Odd cases, but legal + $this->assert(Validator::is_email('jared@[127.0.0.1]')); + $this->assert(Validator::is_email('jared@[ipv6:::1]')); + $this->assert(Validator::is_email('*@domain.tld')); + $this->assert(Validator::is_email("'@domain.tld")); + $this->assert(Validator::is_email('"jared r"@domain.tld')); + + // RFC 6530 + #$this->assert(Validator::is_email('Pelé@example.com')); + #$this->assert(Validator::is_email('δοκιμή@παÏάδειγμα.δοκιμή')); + #$this->assert(Validator::is_email('甲æ–@é»’å·.日本')); } } return 'TestValidation';