From 30cc01f89daea932d15a1a505d25b543913664ac Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 28 Nov 2012 20:21:09 +0100 Subject: Use Delivered-To header as a last resort for identity selection (#1488840) --- program/lib/Roundcube/rcube_imap_generic.php | 10 ++++++---- program/lib/Roundcube/rcube_storage.php | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 70fd6eb2c..ae0bfdd6c 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2206,10 +2206,13 @@ class rcube_imap_generic } break; default: - if (strlen($field) > 2) { - $result[$id]->others[$field] = $string; + if (strlen($field) < 3) { + break; } - break; + if ($result[$id]->others[$field]) { + $string = array_merge((array)$result[$id]->others[$field], (array)$string); + } + $result[$id]->others[$field] = $string; } } } @@ -2217,7 +2220,6 @@ class rcube_imap_generic // VANISHED response (QRESYNC RFC5162) // Sample: * VANISHED (EARLIER) 300:310,405,411 - else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) { $line = substr($line, strlen($match[0])); $v_data = $this->tokenizeResponse($line, 1); diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index 1556aae41..245d911c0 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -64,6 +64,7 @@ abstract class rcube_storage 'MAIL-FOLLOWUP-TO', 'MAIL-REPLY-TO', 'RETURN-PATH', + 'DELIVERED-TO', ); const UNKNOWN = 0; -- cgit v1.2.3 From 0247b89c38c7f7ef1a2111239c6a1c8c13394d93 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 28 Nov 2012 20:40:07 +0100 Subject: Move code for identity selection to function, move identities formatting to rcube_user::list_identities() --- program/lib/Roundcube/rcube_user.php | 15 ++- program/steps/mail/compose.inc | 185 +++++++++++++++++------------------ 2 files changed, 102 insertions(+), 98 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php index 864f2e098..b027506ac 100644 --- a/program/lib/Roundcube/rcube_user.php +++ b/program/lib/Roundcube/rcube_user.php @@ -240,10 +240,12 @@ class rcube_user /** * Return a list of all identities linked with this user * - * @param string $sql_add Optional WHERE clauses + * @param string $sql_add Optional WHERE clauses + * @param bool $formatted Format identity email and name + * * @return array List of identities */ - function list_identities($sql_add = '') + function list_identities($sql_add = '', $formatted = false) { $result = array(); @@ -255,6 +257,15 @@ class rcube_user $this->ID); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { + if ($formatted) { + $ascii_email = format_email($sql_arr['email']); + $utf8_email = format_email(rcube_utils::idn_to_utf8($ascii_email)); + + $sql_arr['email_ascii'] = $ascii_email; + $sql_arr['email'] = $utf8_email; + $sql_arr['ident'] = format_email_recipient($ascii_email, $ident['name']); + } + $result[] = $sql_arr; } diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 60662b382..c039e42c6 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -249,18 +249,7 @@ else { $MESSAGE->compose = array(); // get user's identities -$MESSAGE->identities = $RCMAIL->user->list_identities(); -if (count($MESSAGE->identities)) -{ - foreach ($MESSAGE->identities as $idx => $ident) { - $ident['email'] = format_email($ident['email']); - $email = format_email(rcube_idn_to_utf8($ident['email'])); - - $MESSAGE->identities[$idx]['email_ascii'] = $ident['email']; - $MESSAGE->identities[$idx]['ident'] = format_email_recipient($ident['email'], $ident['name']); - $MESSAGE->identities[$idx]['email'] = $email; - } -} +$MESSAGE->identities = $RCMAIL->user->list_identities(null, true); // Set From field value if (!empty($_POST['_from'])) { @@ -270,92 +259,10 @@ else if (!empty($COMPOSE['param']['from'])) { $MESSAGE->compose['from'] = $COMPOSE['param']['from']; } else if (count($MESSAGE->identities)) { - $a_recipients = array(); - $a_names = array(); - - // extract all recipients of the reply-message - if (is_object($MESSAGE->headers) && in_array($compose_mode, array(RCUBE_COMPOSE_REPLY, RCUBE_COMPOSE_FORWARD))) - { - $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset); - foreach ($a_to as $addr) { - if (!empty($addr['mailto'])) { - $a_recipients[] = format_email($addr['mailto']); - $a_names[] = $addr['name']; - } - } - - if (!empty($MESSAGE->headers->cc)) { - $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset); - foreach ($a_cc as $addr) { - if (!empty($addr['mailto'])) { - $a_recipients[] = format_email($addr['mailto']); - $a_names[] = $addr['name']; - } - } - } - } - - $from_idx = null; - $found_idx = null; - $default_identity = 0; // default identity is always first on the list - - // Select identity - foreach ($MESSAGE->identities as $idx => $ident) { - // use From header - if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) { - if ($MESSAGE->headers->from == $ident['ident']) { - $from_idx = $idx; - break; - } - } - // reply to yourself - else if ($compose_mode == RCUBE_COMPOSE_REPLY && $MESSAGE->headers->from == $ident['ident']) { - $from_idx = $idx; - break; - } - // use replied message recipients - else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) { - if ($found_idx === null) { - $found_idx = $idx; - } - // match identity name - if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) { - $from_idx = $idx; - break; - } - } - } - - // If matching by name+address doesn't found any amtches, get first found address (identity) - if ($from_idx === null) { - $from_idx = $found_idx; - } - - // Try Return-Path - if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) { - foreach ($MESSAGE->identities as $idx => $ident) { - if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) { - $from_idx = $idx; - break; - } - } - } - - // Fallback using Delivered-To - if ($from_idx === null && ($delivered_to = $MESSAGE->headers->others['delivered-to'])) { - foreach ($MESSAGE->identities as $idx => $ident) { - if (in_array($ident['email_ascii'], $delivered_to)) { - $from_idx = $idx; - break; - } - } - } - - $ident = $MESSAGE->identities[$from_idx !== null ? $from_idx : $default_identity]; - $from_id = $ident['identity_id']; + $ident = rcmail_identity_select($MESSAGE, $MESSAGE->identities, $compose_mode); $MESSAGE->compose['from_email'] = $ident['email']; - $MESSAGE->compose['from'] = $from_id; + $MESSAGE->compose['from'] = $ident['identity_id']; } // Set other headers @@ -472,6 +379,92 @@ $MESSAGE_BODY = rcmail_prepare_message_body(); /****** compose mode functions ********/ +function rcmail_identity_select($MESSAGE, $identities, $compose_mode) +{ + $a_recipients = array(); + $a_names = array(); + + // extract all recipients of the reply-message + if (is_object($MESSAGE->headers) && in_array($compose_mode, array(RCUBE_COMPOSE_REPLY, RCUBE_COMPOSE_FORWARD))) { + $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset); + foreach ($a_to as $addr) { + if (!empty($addr['mailto'])) { + $a_recipients[] = format_email($addr['mailto']); + $a_names[] = $addr['name']; + } + } + + if (!empty($MESSAGE->headers->cc)) { + $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset); + foreach ($a_cc as $addr) { + if (!empty($addr['mailto'])) { + $a_recipients[] = format_email($addr['mailto']); + $a_names[] = $addr['name']; + } + } + } + } + + $from_idx = null; + $found_idx = null; + $default_identity = 0; // default identity is always first on the list + + // Select identity + foreach ($identities as $idx => $ident) { + // use From header + if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) { + if ($MESSAGE->headers->from == $ident['ident']) { + $from_idx = $idx; + break; + } + } + // reply to yourself + else if ($compose_mode == RCUBE_COMPOSE_REPLY && $MESSAGE->headers->from == $ident['ident']) { + $from_idx = $idx; + break; + } + // use replied message recipients + else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) { + if ($found_idx === null) { + $found_idx = $idx; + } + // match identity name + if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) { + $from_idx = $idx; + break; + } + } + } + + // If matching by name+address doesn't found any amtches, get first found address (identity) + if ($from_idx === null) { + $from_idx = $found_idx; + } + + // Try Return-Path + if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) { + foreach ($identities as $idx => $ident) { + if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) { + $from_idx = $idx; + break; + } + } + } + + // Fallback using Delivered-To + if ($from_idx === null && ($delivered_to = $MESSAGE->headers->others['delivered-to'])) { + foreach ($identities as $idx => $ident) { + if (in_array($ident['email_ascii'], $delivered_to)) { + $from_idx = $idx; + break; + } + } + } + + return $identities[$from_idx !== null ? $from_idx : $default_identity]; +} + + function rcmail_compose_headers($attrib) { global $MESSAGE; -- cgit v1.2.3 From 7eb7806b211147b40c191d17a983ba34f26b8f1c Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 29 Nov 2012 13:51:49 +0100 Subject: Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836) --- CHANGELOG | 1 + program/lib/Roundcube/rcube_imap_generic.php | 186 +++++++++++++-------------- 2 files changed, 94 insertions(+), 93 deletions(-) (limited to 'program/lib') diff --git a/CHANGELOG b/CHANGELOG index ae6d27398..a47c95dcf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836) - Fix empty email on identities list after identity update (#1488834) - Add new identities_level: (4) one identity with possibility to edit only signature - Use Delivered-To header as a last resort for identity selection (#1488840) diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index ae0bfdd6c..0f32d83d1 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2410,8 +2410,9 @@ class rcube_imap_generic $partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : ''; // format request - $key = $this->nextTag(); - $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; + $key = $this->nextTag(); + $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; + $result = false; // send request if (!$this->putLine($request)) { @@ -2424,118 +2425,117 @@ class rcube_imap_generic $mode = -1; } - // receive reply line do { - $line = rtrim($this->readLine(1024)); - $a = explode(' ', $line); - } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH'); - - $len = strlen($line); - $result = false; + $line = trim($this->readLine(1024)); - if ($a[2] != 'FETCH') { - } - // handle empty "* X FETCH ()" response - else if ($line[$len-1] == ')' && $line[$len-2] != '(') { - // one line response, get everything between first and last quotes - if (substr($line, -4, 3) == 'NIL') { - // NIL response - $result = ''; - } else { - $from = strpos($line, '"') + 1; - $to = strrpos($line, '"'); - $len = $to - $from; - $result = substr($line, $from, $len); + if (!$line) { + break; } - if ($mode == 1) { - $result = base64_decode($result); - } - else if ($mode == 2) { - $result = quoted_printable_decode($result); - } - else if ($mode == 3) { - $result = convert_uudecode($result); + if (!preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) { + continue; } - } else if ($line[$len-1] == '}') { - // multi-line request, find sizes of content and receive that many bytes - $from = strpos($line, '{') + 1; - $to = strrpos($line, '}'); - $len = $to - $from; - $sizeStr = substr($line, $from, $len); - $bytes = (int)$sizeStr; - $prev = ''; + $line = $m[2]; + $last = substr($line, -1); - while ($bytes > 0) { - $line = $this->readLine(8192); + // handle one line response + if ($line[0] == '(' && $last == ')') { + // tokenize content inside brackets + $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\$)/', '', $line)); + $result = count($tokens) == 1 ? $tokens[0] : false; - if ($line === NULL) { - break; + if ($result !== false) { + if ($mode == 1) { + $result = base64_decode($result); + } + else if ($mode == 2) { + $result = quoted_printable_decode($result); + } + else if ($mode == 3) { + $result = convert_uudecode($result); + } } + } + // response with string literal + else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) { + $bytes = (int) $m[1]; + $prev = ''; + + while ($bytes > 0) { + $line = $this->readLine(8192); - $len = strlen($line); + if ($line === NULL) { + break; + } - if ($len > $bytes) { - $line = substr($line, 0, $bytes); $len = strlen($line); - } - $bytes -= $len; - - // BASE64 - if ($mode == 1) { - $line = rtrim($line, "\t\r\n\0\x0B"); - // create chunks with proper length for base64 decoding - $line = $prev.$line; - $length = strlen($line); - if ($length % 4) { - $length = floor($length / 4) * 4; - $prev = substr($line, $length); - $line = substr($line, 0, $length); + + if ($len > $bytes) { + $line = substr($line, 0, $bytes); + $len = strlen($line); + } + $bytes -= $len; + + // BASE64 + if ($mode == 1) { + $line = rtrim($line, "\t\r\n\0\x0B"); + // create chunks with proper length for base64 decoding + $line = $prev.$line; + $length = strlen($line); + if ($length % 4) { + $length = floor($length / 4) * 4; + $prev = substr($line, $length); + $line = substr($line, 0, $length); + } + else { + $prev = ''; + } + $line = base64_decode($line); + } + // QUOTED-PRINTABLE + else if ($mode == 2) { + $line = rtrim($line, "\t\r\0\x0B"); + $line = quoted_printable_decode($line); + } + // UUENCODE + else if ($mode == 3) { + $line = rtrim($line, "\t\r\n\0\x0B"); + if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) { + continue; + } + $line = convert_uudecode($line); + } + // default + else if ($formatted) { + $line = rtrim($line, "\t\r\n\0\x0B") . "\n"; } - else - $prev = ''; - $line = base64_decode($line); - // QUOTED-PRINTABLE - } else if ($mode == 2) { - $line = rtrim($line, "\t\r\0\x0B"); - $line = quoted_printable_decode($line); - // UUENCODE - } else if ($mode == 3) { - $line = rtrim($line, "\t\r\n\0\x0B"); - if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) - continue; - $line = convert_uudecode($line); - // default - } else if ($formatted) { - $line = rtrim($line, "\t\r\n\0\x0B") . "\n"; - } - if ($file) { - if (fwrite($file, $line) === false) - break; + if ($file) { + if (fwrite($file, $line) === false) { + break; + } + } + else if ($print) { + echo $line; + } + else { + $result .= $line; + } } - else if ($print) - echo $line; - else - $result .= $line; } - } - - // read in anything up until last line - if (!$end) - do { - $line = $this->readLine(1024); - } while (!$this->startsWith($line, $key, true)); + } while (!$this->startsWith($line, $key, true)); if ($result !== false) { if ($file) { return fwrite($file, $result); - } else if ($print) { + } + else if ($print) { echo $result; - } else - return $result; - return true; + return true; + } + + return $result; } return false; -- cgit v1.2.3 From 0fa54df638a0b0f514d1bfba3cefb93e38991a35 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sat, 1 Dec 2012 20:02:34 +0100 Subject: enriched.inc -> rcube_enriched --- program/include/bc.php | 6 +- program/lib/Roundcube/rcube_enriched.php | 147 +++++++++++++++++++++++++++++++ program/lib/enriched.inc | 114 ------------------------ program/steps/mail/compose.inc | 9 +- program/steps/mail/func.inc | 3 +- tests/Framework/Enriched.php | 74 ++++++++++++++++ tests/phpunit.xml | 1 + 7 files changed, 231 insertions(+), 123 deletions(-) create mode 100644 program/lib/Roundcube/rcube_enriched.php delete mode 100644 program/lib/enriched.inc create mode 100644 tests/Framework/Enriched.php (limited to 'program/lib') diff --git a/program/include/bc.php b/program/include/bc.php index 5047e0a84..12110c0ad 100644 --- a/program/include/bc.php +++ b/program/include/bc.php @@ -399,7 +399,11 @@ function get_boolean($str) return rcube_utils::get_boolean($str); } +function enriched_to_html($data) +{ + return rcube_enriched::to_html($data); +} + class rcube_html_page extends rcmail_html_page { - } diff --git a/program/lib/Roundcube/rcube_enriched.php b/program/lib/Roundcube/rcube_enriched.php new file mode 100644 index 000000000..8b64fe054 --- /dev/null +++ b/program/lib/Roundcube/rcube_enriched.php @@ -0,0 +1,147 @@ + | + | Author: Ryo Chijiiwa (IlohaMail) | + +-----------------------------------------------------------------------+ +*/ + + +/** + * Class for Enriched to HTML conversion + * + * @package Framework + * @subpackage Utils + */ +class rcube_enriched +{ + protected static function convert_newlines($body) + { + // remove single newlines, convert N newlines to N-1 + $body = str_replace("\r\n", "\n", $body); + $len = strlen($body); + $nl = 0; + $out = ''; + + for ($i=0; $i<$len; $i++) { + $c = $body[$i]; + if (ord($c) == 10) + $nl++; + if ($nl && ord($c) != 10) + $nl = 0; + if ($nl != 1) + $out .= $c; + else + $out .= ' '; + } + + return $out; + } + + protected static function convert_formatting($body) + { + $replace = array( + '' => '', '' => '', + '' => '', '' => '', + '' => '', '' => '', + '' => '', ''=> '', + '' => '', '' => '', + '' => '', '' => '', + '' => '', '' => '', + '' => '', '' => '', + '' => '', '' => '', + '' => '', '' => '', + '' => '', '' => '', + ); + + return str_ireplace(array_keys($replace), array_values($replace), $body); + } + + protected static function convert_font($body) + { + $pattern = '/(.*)\\(.*)\<\/param\>(.*)\<\/fontfamily\>(.*)/ims'; + + while (preg_match($pattern, $body, $a)) { + if (count($a) != 5) + continue; + + $body = $a[1].''.$a[3].''.$a[4]; + } + + return $body; + } + + protected static function convert_color($body) + { + $pattern = '/(.*)\\(.*)\<\/param\>(.*)\<\/color\>(.*)/ims'; + + while (preg_match($pattern, $body, $a)) { + if (count($a) != 5) + continue; + + // extract color (either by name, or ####,####,####) + if (strpos($a[2],',')) { + $rgb = explode(',',$a[2]); + $color = '#'; + for ($i=0; $i<3; $i++) + $color .= substr($rgb[$i], 0, 2); // just take first 2 bytes + } + else { + $color = $a[2]; + } + + // put it all together + $body = $a[1].''.$a[3].''.$a[4]; + } + + return $body; + } + + protected static function convert_excerpt($body) + { + $pattern = '/(.*)\(.*)\<\/excerpt\>(.*)/i'; + + while (preg_match($pattern, $body, $a)) { + if (count($a) != 4) + continue; + + $quoted = ''; + $lines = explode('
', $a[2]); + + foreach ($lines as $n => $line) + $quoted .= '>'.$line.'
'; + + $body = $a[1].''.$quoted.''.$a[3]; + } + + return $body; + } + + public static function to_html($body) + { + $body = str_replace('<<','<',$body); + $body = self::convert_newlines($body); + $body = str_replace("\n", '
', $body); + $body = self::convert_formatting($body); + $body = self::convert_color($body); + $body = self::convert_font($body); + $body = self::convert_excerpt($body); + //$body = nl2br($body); + + return $body; + } +} diff --git a/program/lib/enriched.inc b/program/lib/enriched.inc deleted file mode 100644 index e3abd8c4f..000000000 --- a/program/lib/enriched.inc +++ /dev/null @@ -1,114 +0,0 @@ -'=>'',''=>'',''=>'', - ''=>'',''=>'',''=>'', - ''=>'',''=>'', - ''=>'',''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>'', - ''=>''); - - while(list($find,$replace)=each($a)){ - $body = preg_replace('#'.$find.'#i', $replace, $body); - } - return $body; -} - -function enriched_font($body){ - $pattern = '/(.*)\\(.*)\<\/param\>(.*)\<\/fontfamily\>(.*)/ims'; - while(preg_match($pattern,$body,$a)){ - //print_r($a); - if (count($a)!=5) continue; - $body=$a[1].''.$a[3].''.$a[4]; - } - - return $body; -} - - -function enriched_color($body){ - $pattern = '/(.*)\\(.*)\<\/param\>(.*)\<\/color\>(.*)/ims'; - while(preg_match($pattern,$body,$a)){ - //print_r($a); - if (count($a)!=5) continue; - - //extract color (either by name, or ####,####,####) - if (strpos($a[2],',')){ - $rgb = explode(',',$a[2]); - $color ='#'; - for($i=0;$i<3;$i++) $color.=substr($rgb[$i],0,2); //just take first 2 bytes - }else{ - $color = $a[2]; - } - - //put it all together - $body = $a[1].''.$a[3].''.$a[4]; - } - - return $body; -} - -function enriched_excerpt($body){ - - $pattern = '/(.*)\(.*)\<\/excerpt\>(.*)/i'; - while(preg_match($pattern,$body,$a)){ - //print_r($a); - if (count($a)!=4) continue; - $quoted = ''; - $lines = explode('
',$a[2]); - foreach($lines as $n=>$line) $quoted.='>'.$line.'
'; - $body=$a[1].''.$quoted.''.$a[3]; - } - - return $body; -} - -function enriched_to_html($body){ - $body = str_replace('<<','<',$body); - $body = enriched_convert_newlines($body); - $body = str_replace("\n", '
', $body); - $body = enriched_convert_formatting($body); - $body = enriched_color($body); - $body = enriched_font($body); - $body = enriched_excerpt($body); - //$body = nl2br($body); - return $body; -} - -?> \ No newline at end of file diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index c039e42c6..96391c88b 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -733,8 +733,7 @@ function rcmail_compose_part_body($part, $isHtml = false) if ($part->ctype_secondary == 'html') { } else if ($part->ctype_secondary == 'enriched') { - require_once(INSTALL_PATH . 'program/lib/enriched.inc'); - $body = enriched_to_html($body); + $body = rcube_enriched::to_html($body); } else { // try to remove the signature @@ -750,8 +749,7 @@ function rcmail_compose_part_body($part, $isHtml = false) } else { if ($part->ctype_secondary == 'enriched') { - require_once(INSTALL_PATH . 'program/lib/enriched.inc'); - $body = enriched_to_html($body); + $body = rcube_enriched::to_html($body); $part->ctype_secondary = 'html'; } @@ -763,8 +761,7 @@ function rcmail_compose_part_body($part, $isHtml = false) $body = $txt->get_text(); } else if ($part->ctype_secondary == 'enriched') { - require_once(INSTALL_PATH . 'program/lib/enriched.inc'); - $body = enriched_to_html($body); + $body = rcube_enriched::to_html($body); } else { if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') { diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index cb1a5ddae..80dac716e 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -751,8 +751,7 @@ function rcmail_print_body($part, $p = array()) } // text/enriched else if ($data['type'] == 'enriched') { - require_once(INSTALL_PATH . 'program/lib/enriched.inc'); - $body = enriched_to_html($data['body']); + $body = rcube_enriched::to_html($data['body']); $body = rcmail_wash_html($body, $data, $part->replaces); $part->ctype_secondary = 'html'; } diff --git a/tests/Framework/Enriched.php b/tests/Framework/Enriched.php new file mode 100644 index 000000000..26bbc3b4e --- /dev/null +++ b/tests/Framework/Enriched.php @@ -0,0 +1,74 @@ +assertInstanceOf('rcube_enriched', $object, "Class constructor"); + } + + /** + * Test to_html() + */ + function test_to_html() + { + $enriched = 'the-text'; + $expected = 'the-text'; + $result = rcube_enriched::to_html($enriched); + + $this->assertSame($expected, $result); + } + + /** + * Data for test_formatting() + */ + function data_formatting() + { + return array( + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + array('', ''), + ); + } + + /** + * Test formatting conversion + * @dataProvider data_formatting + */ + function test_formatting($enriched, $expected) + { + $result = rcube_enriched::to_html($enriched); + + $this->assertSame($expected, $result); + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 36ab6d714..c9e229e97 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -10,6 +10,7 @@ Framework/Charset.php Framework/ContentFilter.php Framework/Csv2vcard.php + Framework/Enriched.php Framework/Html.php Framework/Imap.php Framework/ImapGeneric.php -- cgit v1.2.3 From 74cd0a9b62f11bc07c5a1d3ba0098b54883eb0ba Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 4 Dec 2012 09:17:08 +0100 Subject: - Fix XSS vulnerability in vbscript: and data:text links handling (#1488850) --- CHANGELOG | 1 + program/lib/washtml.php | 2 +- tests/MailFunc.php | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/CHANGELOG b/CHANGELOG index a47c95dcf..af7d29c04 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix XSS vulnerability in vbscript: and data:text links handling (#1488850) - Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836) - Fix empty email on identities list after identity update (#1488834) - Add new identities_level: (4) one identity with possibility to edit only signature diff --git a/program/lib/washtml.php b/program/lib/washtml.php index 0d4ffdb4b..d13d66404 100644 --- a/program/lib/washtml.php +++ b/program/lib/washtml.php @@ -214,7 +214,7 @@ class washtml $key = strtolower($key); $value = $node->getAttribute($key); if (isset($this->_html_attribs[$key]) || - ($key == 'href' && !preg_match('!^javascript!i', $value) + ($key == 'href' && !preg_match('!^(javascript|vbscript|data:text)!i', $value) && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value)) ) { $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"'; diff --git a/tests/MailFunc.php b/tests/MailFunc.php index 967277c2a..4d4250c22 100644 --- a/tests/MailFunc.php +++ b/tests/MailFunc.php @@ -96,6 +96,20 @@ class MailFunc extends PHPUnit_Framework_TestCase $this->assertNotRegExp('/font-style:italic/', $washed, "Allow valid styles"); } + /** + * Test the elimination of some XSS vulnerabilities + */ + function test_html_xss3() + { + // #1488850 + $html = '

Firefox' + .'Internet Explorer

'; + $washed = rcmail_wash_html($html, array('safe' => true), array()); + + $this->assertNotRegExp('/data:text/', $washed, "Remove data:text/html links"); + $this->assertNotRegExp('/vbscript:/', $washed, "Remove vbscript: links"); + } + /** * Test washtml class on non-unicode characters (#1487813) */ -- cgit v1.2.3 From 996af3bfd9bfcac84396790a9a215d177b17c79e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 7 Dec 2012 11:13:11 +0100 Subject: Some more rcmail -> rcube cleanup --- program/lib/Roundcube/rcube.php | 2 +- program/lib/Roundcube/rcube_addressbook.php | 4 ++-- program/lib/Roundcube/rcube_plugin.php | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index c3aa8ffa5..cc4905a14 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -36,7 +36,7 @@ class rcube /** * Singleton instace of rcube * - * @var rcmail + * @var rcube */ static protected $instance; diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php index d14fc587a..ea8df700c 100644 --- a/program/lib/Roundcube/rcube_addressbook.php +++ b/program/lib/Roundcube/rcube_addressbook.php @@ -209,13 +209,13 @@ abstract class rcube_addressbook */ public function validate(&$save_data, $autofix = false) { - $rcmail = rcube::get_instance(); + $rcube = rcube::get_instance(); // check validity of email addresses foreach ($this->get_col_values('email', $save_data, true) as $email) { if (strlen($email)) { if (!rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) { - $error = $rcmail->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email))); + $error = $rcube->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email))); $this->set_error(self::ERROR_VALIDATE, $error); return false; } diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php index dbb15e8be..5db85025d 100644 --- a/program/lib/Roundcube/rcube_plugin.php +++ b/program/lib/Roundcube/rcube_plugin.php @@ -203,23 +203,23 @@ abstract class rcube_plugin foreach ($texts as $key => $value) $add[$domain.'.'.$key] = $value; - $rcmail = rcube::get_instance(); - $rcmail->load_language($lang, $add); + $rcube = rcube::get_instance(); + $rcube->load_language($lang, $add); // add labels to client if ($add2client) { $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add); - $rcmail->output->add_label($js_labels); + $rcube->output->add_label($js_labels); } } } /** - * Wrapper for rcmail::gettext() adding the plugin ID as domain + * Wrapper for rcube::gettext() adding the plugin ID as domain * * @param string $p Message identifier * @return string Localized text - * @see rcmail::gettext() + * @see rcube::gettext() */ public function gettext($p) { @@ -336,8 +336,8 @@ abstract class rcube_plugin */ public function local_skin_path() { - $rcmail = rcube::get_instance(); - foreach (array($rcmail->config->get('skin'), 'larry') as $skin) { + $rcube = rcube::get_instance(); + foreach (array($rcube->config->get('skin'), 'larry') as $skin) { $skin_path = 'skins/' . $skin; if (is_dir(realpath(slashify($this->home) . $skin_path))) break; -- cgit v1.2.3 From a3985963f0df4fffa1a6d272c777f781ebd86d50 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 7 Dec 2012 12:38:08 +0100 Subject: Fix big memory consumption of DB layer (#1488856) --- CHANGELOG | 1 + program/lib/Roundcube/rcube_db.php | 100 ++++++++++++------------------------- 2 files changed, 32 insertions(+), 69 deletions(-) (limited to 'program/lib') diff --git a/CHANGELOG b/CHANGELOG index 5a1b1acd5..79f19b904 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix big memory consumption of DB layer (#1488856) - Add workaround for IE<=8 bug where Content-Disposition:inline was ignored (#1488844) - Fix XSS vulnerability in vbscript: and data:text links handling (#1488850) - Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836) diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 5d8c4a534..2c471e74d 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -37,12 +37,11 @@ class rcube_db protected $db_mode; // Connection mode protected $dbh; // Connection handle - protected $db_error = false; - protected $db_error_msg = ''; - protected $conn_failure = false; - protected $a_query_results = array('dummy'); - protected $last_res_id = 0; - protected $db_index = 0; + protected $db_error = false; + protected $db_error_msg = ''; + protected $conn_failure = false; + protected $db_index = 0; + protected $last_result; protected $tables; protected $variables; @@ -267,14 +266,14 @@ class rcube_db /** * Getter for error state * - * @param int $res_id Optional query result identifier + * @param mixed $result Optional query result * * @return string Error message */ - public function is_error($res_id = null) + public function is_error($result = null) { - if ($res_id !== null) { - return $this->_get_result($res_id) === false ? $this->db_error_msg : null; + if ($result !== null) { + return $result === false ? $this->db_error_msg : null; } return $this->db_error ? $this->db_error_msg : null; @@ -343,7 +342,7 @@ class rcube_db * @param int Number of rows for LIMIT statement * @param mixed Values to be inserted in query * - * @return int Query handle identifier + * @return PDOStatement|bool Query handle or False on error */ public function limitquery() { @@ -363,7 +362,7 @@ class rcube_db * @param int $numrows Number of rows for LIMIT statement * @param array $params Values to be inserted in query * - * @return int Query handle identifier + * @return PDOStatement|bool Query handle or False on error */ protected function _query($query, $offset, $numrows, $params) { @@ -374,7 +373,7 @@ class rcube_db // check connection before proceeding if (!$this->is_connected()) { - return null; + return $this->last_result = false; } if ($numrows || $offset) { @@ -417,20 +416,21 @@ class rcube_db 'message' => $this->db_error_msg), true, false); } - // add result, even if it's an error - return $this->_add_result($query); + $this->last_result = $query; + + return $query; } /** * Get number of affected rows for the last query * - * @param number $res_id Optional query handle identifier + * @param mixed $result Optional query handle * * @return int Number of rows or false on failure */ - public function affected_rows($res_id = null) + public function affected_rows($result = null) { - if ($result = $this->_get_result($res_id)) { + if ($result || ($result === null && ($result = $this->last_result))) { return $result->rowCount(); } @@ -464,13 +464,12 @@ class rcube_db * Get an associative array for one row * If no query handle is specified, the last query will be taken as reference * - * @param int $res_id Optional query handle identifier + * @param mixed $result Optional query handle * * @return mixed Array with col values or false on failure */ - public function fetch_assoc($res_id = null) + public function fetch_assoc($result = null) { - $result = $this->_get_result($res_id); return $this->_fetch_row($result, PDO::FETCH_ASSOC); } @@ -478,31 +477,30 @@ class rcube_db * Get an index array for one row * If no query handle is specified, the last query will be taken as reference * - * @param int $res_id Optional query handle identifier + * @param mixed $result Optional query handle * * @return mixed Array with col values or false on failure */ - public function fetch_array($res_id = null) + public function fetch_array($result = null) { - $result = $this->_get_result($res_id); return $this->_fetch_row($result, PDO::FETCH_NUM); } /** * Get col values for a result row * - * @param PDOStatement $result Result handle - * @param int $mode Fetch mode identifier + * @param mixed $result Optional query handle + * @param int $mode Fetch mode identifier * * @return mixed Array with col values or false on failure */ protected function _fetch_row($result, $mode) { - if (!is_object($result) || !$this->is_connected()) { - return false; + if ($result || ($result === null && ($result = $this->last_result))) { + return $result->fetch($mode); } - return $result->fetch($mode); + return false; } /** @@ -538,8 +536,8 @@ class rcube_db if ($this->tables === null) { $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME'); - if ($res = $this->_get_result($q)) { - $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0); + if ($q) { + $this->tables = $q->fetchAll(PDO::FETCH_COLUMN, 0); } else { $this->tables = array(); @@ -561,8 +559,8 @@ class rcube_db $q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?', array($table)); - if ($res = $this->_get_result($q)) { - return $res->fetchAll(PDO::FETCH_COLUMN, 0); + if ($q) { + return $q->fetchAll(PDO::FETCH_COLUMN, 0); } return array(); @@ -776,42 +774,6 @@ class rcube_db return utf8_decode($input); } - /** - * Adds a query result and returns a handle ID - * - * @param object $res Query handle - * - * @return int Handle ID - */ - protected function _add_result($res) - { - $this->last_res_id = sizeof($this->a_query_results); - $this->a_query_results[$this->last_res_id] = $res; - - return $this->last_res_id; - } - - /** - * Resolves a given handle ID and returns the according query handle - * If no ID is specified, the last resource handle will be returned - * - * @param int $res_id Handle ID - * - * @return mixed Resource handle or false on failure - */ - protected function _get_result($res_id = null) - { - if ($res_id == null) { - $res_id = $this->last_res_id; - } - - if (!empty($this->a_query_results[$res_id])) { - return $this->a_query_results[$res_id]; - } - - return false; - } - /** * Return correct name for a specific database table * -- cgit v1.2.3 From 7c5d4b0d4d01c66768a71e8e6138f5b637d44e4a Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 7 Dec 2012 14:54:35 +0100 Subject: Fix typo in identity data parser ('ident' item wasn't set correctly) --- program/lib/Roundcube/rcube_user.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php index b027506ac..f6b77f5e1 100644 --- a/program/lib/Roundcube/rcube_user.php +++ b/program/lib/Roundcube/rcube_user.php @@ -263,7 +263,7 @@ class rcube_user $sql_arr['email_ascii'] = $ascii_email; $sql_arr['email'] = $utf8_email; - $sql_arr['ident'] = format_email_recipient($ascii_email, $ident['name']); + $sql_arr['ident'] = format_email_recipient($ascii_email, $sql_arr['name']); } $result[] = $sql_arr; -- cgit v1.2.3 From bc1ec6c1a103632e4809bf2ee1c39e486bfe3038 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sat, 8 Dec 2012 14:53:28 +0100 Subject: Added README file for the Roundcube framework --- program/lib/Roundcube/README.md | 101 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 program/lib/Roundcube/README.md (limited to 'program/lib') diff --git a/program/lib/Roundcube/README.md b/program/lib/Roundcube/README.md new file mode 100644 index 000000000..06c6f6a53 --- /dev/null +++ b/program/lib/Roundcube/README.md @@ -0,0 +1,101 @@ +Roundcube Framework +=================== + +INTRODUCTION +------------ +The Roundcube Framework is the basic library used for the Roundcube Webmail +application. It is an extract of classes providing the core functionality for +an email system. They can be used individually or as package for the following +tasks: + +- IMAP mailbox access with optional caching +- MIME message handling +- Email message creation and sending through SMTP +- General caching utilities using the local database +- Database abstraction using PDO +- VCard parsing and writing + + +INSTALLATION +------------ +Copy all files of this directory to your project or install it in the default +include_path directory of your webserver. Some classes of the framework require +one or multiple of the following [PEAR][pear] libraries: + +- Mail_Mime 1.8.1 or newer +- Mail_mimeDecode 1.5.5 or newer +- Net_SMTP (latest from https://github.com/pear/Net_SMTP/) +- Net_IDNA2 0.1.1 or newer +- Auth_SASL 1.0.6 or newer + + +USAGE +----- +The Roundcube Framework provides a bootstrapping file which registers an +autoloader and sets up the environment necessary for the Roundcube classes. +In order to make use of the framework, simply include the bootstrap.php file +from this directory in your application and start using the classes by simply +instantiating them. + +If you wanna use more complex functionality like IMAP access with database +caching or plugins, the rcube singleton helps you loading the necessary files: + +'); +define('RCUBE_PLUGINS_DIR', 'get_storage(); + +// do cool stuff here... + +?> + + +LICENSE +------- +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License (**with exceptions +for plugins**) as published by the Free Software Foundation, either +version 3 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see [www.gnu.org/licenses/][gpl]. + +This file forms part of the Roundcube Webmail Framework for which the +following exception is added: Plugins which merely make function calls to the +Roundcube Webmail Framework, and for that purpose include it by reference +shall not be considered modifications of the software. + +If you wish to use this file in another project or create a modified +version that will not be part of the Roundcube Webmail Framework, you +may remove the exception above and use this source code under the +original version of the license. + +For more details about licensing and the exceptions for skins and plugins +see [roundcube.net/license][license] + + +CONTACT +------- +For any bug reports or feature requests please refer to the tracking system +at [trac.roundcube.net][tracreport] or subscribe to our mailing list. +See [roundcube.net/support][support] for details. + +You're always welcome to send a message to the project admins: +hello(at)roundcube(dot)net + + +[pear]: http://pear.php.net +[gpl]: http://www.gnu.org/licenses/ +[license]: http://roundcube.net/license +[support]: http://roundcube.net/support +[tracreport]: http://trac.roundcube.net/wiki/Howto_ReportIssues \ No newline at end of file -- cgit v1.2.3 From d414cc05a34eacc579a0678f59f433a7fb11d7bb Mon Sep 17 00:00:00 2001 From: "Thomas B." Date: Sat, 8 Dec 2012 15:01:55 +0100 Subject: Add github syntax highlighting to php code snippet --- program/lib/Roundcube/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/README.md b/program/lib/Roundcube/README.md index 06c6f6a53..88f2d076e 100644 --- a/program/lib/Roundcube/README.md +++ b/program/lib/Roundcube/README.md @@ -40,6 +40,7 @@ instantiating them. If you wanna use more complex functionality like IMAP access with database caching or plugins, the rcube singleton helps you loading the necessary files: +```php '); @@ -53,7 +54,7 @@ $imap = $rcube->get_storage(); // do cool stuff here... ?> - +``` LICENSE ------- -- cgit v1.2.3 From 3bb75a5cc7a7c77950eeb6070631c9c8b76a5a16 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sat, 8 Dec 2012 15:36:55 +0100 Subject: Add default path for mime.types file; map jpg => image/jpeg in fallback list --- program/lib/Roundcube/rcube_mime.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index 17cb3f015..4bb5b483f 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -717,6 +717,7 @@ class rcube_mime $file_paths[] = $mime_types; // try common locations + $file_paths[] = '/etc/mime.types'; $file_paths[] = '/etc/httpd/mime.types'; $file_paths[] = '/etc/httpd2/mime.types'; $file_paths[] = '/etc/apache/mime.types'; @@ -749,7 +750,7 @@ class rcube_mime // fallback to some well-known types most important for daily emails if (empty($mime_types)) { $mime_extensions = @include(RCUBE_CONFIG_DIR . '/mimetypes.php'); - $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff'); + $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff'); foreach ($mime_extensions as $ext => $mime) $mime_types[$mime][] = $ext; -- cgit v1.2.3 From 4f1c887eaaadcc2b6ab2c6578481991698ea74a3 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 9 Dec 2012 12:12:27 +0100 Subject: Add support for IMAP BINARY (RFC3516) --- program/lib/Roundcube/rcube_imap.php | 7 ++++--- program/lib/Roundcube/rcube_imap_generic.php | 26 ++++++++++++++++++-------- 2 files changed, 22 insertions(+), 11 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 8ca24dec7..a2495462a 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -2226,10 +2226,11 @@ class rcube_imap extends rcube_storage * @param boolean $is_file True if $message is a filename * @param array $flags Message flags * @param mixed $date Message internal date + * @param bool $binary Enables BINARY append * * @return int|bool Appended message UID or True on success, False on error */ - public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null) + public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null, $binary = false) { if (!strlen($folder)) { $folder = $this->folder; @@ -2247,10 +2248,10 @@ class rcube_imap extends rcube_storage $date = $this->date_format($date); if ($is_file) { - $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date); + $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date, $binary); } else { - $saved = $this->conn->append($folder, $message, $flags, $date); + $saved = $this->conn->append($folder, $message, $flags, $date, $binary); } if ($saved) { diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 0f32d83d1..28d56c16f 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2548,10 +2548,11 @@ class rcube_imap_generic * @param string $message Message content * @param array $flags Message flags * @param string $date Message internal date + * @param bool $binary Enable BINARY append (RFC3516) * * @return string|bool On success APPENDUID response (if available) or True, False on failure */ - function append($mailbox, &$message, $flags = array(), $date = null) + function append($mailbox, &$message, $flags = array(), $date = null, $binary = false) { unset($this->data['APPENDUID']); @@ -2559,8 +2560,13 @@ class rcube_imap_generic return false; } - $message = str_replace("\r", '', $message); - $message = str_replace("\n", "\r\n", $message); + $binary = $binary && $this->getCapability('BINARY'); + $literal_plus = !$binary && $this->prefs['literal+']; + + if (!$binary) { + $message = str_replace("\r", '', $message); + $message = str_replace("\n", "\r\n", $message); + } $len = strlen($message); if (!$len) { @@ -2573,12 +2579,12 @@ class rcube_imap_generic if (!empty($date)) { $request .= ' ' . $this->escape($date); } - $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}'; + $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}'; // send APPEND command if ($this->putLine($request)) { // Do not wait when LITERAL+ is supported - if (!$this->prefs['literal+']) { + if (!$literal_plus) { $line = $this->readReply(); if ($line[0] != '+') { @@ -2620,10 +2626,11 @@ class rcube_imap_generic * @param string $headers Message headers * @param array $flags Message flags * @param string $date Message internal date + * @param bool $binary Enable BINARY append (RFC3516) * * @return string|bool On success APPENDUID response (if available) or True, False on failure */ - function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null) + function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false) { unset($this->data['APPENDUID']); @@ -2654,18 +2661,21 @@ class rcube_imap_generic $len += strlen($headers) + strlen($body_separator); } + $binary = $binary && $this->getCapability('BINARY'); + $literal_plus = !$binary && $this->prefs['literal+']; + // build APPEND command $key = $this->nextTag(); $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')'; if (!empty($date)) { $request .= ' ' . $this->escape($date); } - $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}'; + $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}'; // send APPEND command if ($this->putLine($request)) { // Don't wait when LITERAL+ is supported - if (!$this->prefs['literal+']) { + if (!$literal_plus) { $line = $this->readReply(); if ($line[0] != '+') { -- cgit v1.2.3 From 1aaa4bc3937187c54039a8459409a2efd9cb3cd7 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 10 Dec 2012 12:42:45 +0100 Subject: Fix PHP Warning: ldap_parse_virtuallist_control() expects parameter 2 to be resource, null given. This happens on Administrative Limit Exceeded error when using VLV. --- program/lib/Roundcube/rcube_ldap.php | 1 + 1 file changed, 1 insertion(+) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index c9a14d863..c32cea728 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -1455,6 +1455,7 @@ class rcube_ldap extends rcube_addressbook if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { if (ldap_parse_result($this->conn, $this->ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls) + && $serverctrls // can be null e.g. in case of adm. limit error ) { ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $this->vlv_count, $vresult); -- cgit v1.2.3 From a8a72e2e7ee89caa04f8f13b6067e1b4ad870612 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 10 Dec 2012 22:26:45 +0100 Subject: Nicely render headers of message/rfc822 parts --- program/lib/Roundcube/rcube_message.php | 9 +++++++- program/lib/Roundcube/rcube_message_header.php | 16 +++++++++++++ program/steps/mail/func.inc | 31 +++++++++++++++++++------- skins/classic/mail.css | 11 +++++++++ skins/larry/mail.css | 24 ++++++++++++++++++-- skins/larry/templates/message.html | 2 +- skins/larry/templates/messagepreview.html | 2 +- 7 files changed, 82 insertions(+), 13 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index 4ef534a0a..c626af08a 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -320,8 +320,15 @@ class rcube_message private function parse_structure($structure, $recursive = false) { // real content-type of message/rfc822 part - if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype) + if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype) { $mimetype = $structure->real_mimetype; + + // parse headers from message/rfc822 part + if (!isset($structure->headers['subject'])) { + list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 4096)); + $structure->headers = rcube_mime::parse_headers($headers); + } + } else $mimetype = $structure->mimetype; diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php index 445d0bd39..7009a00af 100644 --- a/program/lib/Roundcube/rcube_message_header.php +++ b/program/lib/Roundcube/rcube_message_header.php @@ -235,6 +235,22 @@ class rcube_message_header $this->others[$name] = $value; } } + + + /** + * Factory method to instantiate headers from a data array + * + * @param array Hash array with header values + * @return object rcube_message_header instance filled with headers values + */ + public static function from_array($arr) + { + $obj = new rcube_message_header; + foreach ($arr as $k => $v) + $obj->set($k, $v); + + return $obj; + } } diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 80dac716e..8ae41017e 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -5,7 +5,7 @@ | program/steps/mail/func.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2010, The Roundcube Dev Team | + | Copyright (C) 2005-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -939,13 +939,13 @@ function rcmail_html_tag_callback($matches) /** * return table with message headers */ -function rcmail_message_headers($attrib, $headers=NULL) +function rcmail_message_headers($attrib, $headers=null) { global $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL; static $sa_attrib; // keep header table attrib - if (is_array($attrib) && !$sa_attrib) + if (is_array($attrib) && !$sa_attrib && !$attrib['valueof']) $sa_attrib = $attrib; else if (!is_array($attrib) && is_array($sa_attrib)) $attrib = $sa_attrib; @@ -954,8 +954,13 @@ function rcmail_message_headers($attrib, $headers=NULL) return FALSE; // get associative array of headers object - if (!$headers) - $headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers; + if (!$headers) { + $headers_obj = $MESSAGE->headers; + $headers = get_object_vars($MESSAGE->headers); + } + else { + $headers_obj = rcube_message_header::from_array($headers); + } // show these headers $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto', @@ -1031,7 +1036,7 @@ function rcmail_message_headers($attrib, $headers=NULL) } $plugin = $RCMAIL->plugins->exec_hook('message_headers_output', - array('output' => $output_headers, 'headers' => $MESSAGE->headers, 'exclude' => $exclude_headers)); + array('output' => $output_headers, 'headers' => $headers_obj, 'exclude' => $exclude_headers)); // single header value is requested if (!empty($attrib['valueof'])) @@ -1110,8 +1115,9 @@ function rcmail_message_body($attrib) if (!empty($MESSAGE->parts)) { foreach ($MESSAGE->parts as $i => $part) { - if ($part->type == 'headers') - $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers); + if ($part->type == 'headers') { + $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers)); + } else if ($part->type == 'content') { // unsapported if ($part->realtype) { @@ -1139,6 +1145,15 @@ function rcmail_message_body($attrib) if (!isset($part->body)) $part->body = $MESSAGE->get_part_content($part->mime_id); + // extract headers from message/rfc822 parts + if ($part->mimetype == 'message/rfc822') { + list($hdrs, $body) = explode("\r\n\r\n", $part->body, 2); + if ($hdrs && $body && preg_match('/^[\w-]+:\s/i', $hdrs)) { + $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, rcube_mime::parse_headers($hdrs))); + $part->body = $body; + } + } + // message is cached but not exists (#1485443), or other error if ($part->body === false) { rcmail_message_error($MESSAGE->uid); diff --git a/skins/classic/mail.css b/skins/classic/mail.css index 85c53d569..98325d9d2 100644 --- a/skins/classic/mail.css +++ b/skins/classic/mail.css @@ -1070,6 +1070,17 @@ table.headers-table background-color: #F4F4F4; } +#messagebody table.headers-table +{ + margin: 16px 6px 6px 6px; +} + +div.message-partheaders + div.message-part +{ + border-top: 0; + padding-top: 4px; +} + table.headers-table tr td { font-size: 11px; diff --git a/skins/larry/mail.css b/skins/larry/mail.css index 6512e52a3..12a2b7a82 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -999,12 +999,14 @@ div.hide-headers { } div.message-part, -div.message-htmlpart { - padding: 0 2px 10px 2px; +div.message-htmlpart, +div.message-partheaders { + padding: 10px 2px; border-top: 1px solid #ccc; } #messagebody div:first-child { + padding-top: 0; border-top: 0; } @@ -1045,6 +1047,24 @@ div.message-part blockquote blockquote blockquote { border-right: 2px solid #bb0000; } +div.message-partheaders { + margin-top: 8px; + padding: 8px 0; +} + +div.message-partheaders .headers-table { + width: 100%; +} + +div.message-partheaders .headers-table td.header-title { + width: auto; + padding-left: 0; +} + +div.message-partheaders .headers-table td.header { + width: 88%; +} + #messagebody > hr { color: #fff; background: #fff; diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html index f7e188f5f..04381f5e9 100644 --- a/skins/larry/templates/message.html +++ b/skins/larry/templates/message.html @@ -64,7 +64,7 @@
- +
diff --git a/skins/larry/templates/messagepreview.html b/skins/larry/templates/messagepreview.html index 9eb4d1e00..aef282ac9 100644 --- a/skins/larry/templates/messagepreview.html +++ b/skins/larry/templates/messagepreview.html @@ -47,7 +47,7 @@
- +
-- cgit v1.2.3 From bb5d7282855dd83ccdd211cb77d0776dce71468e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 12 Dec 2012 08:54:33 +0100 Subject: Use also Envelope-To for identity selection (#1488553) --- CHANGELOG | 2 +- program/lib/Roundcube/rcube_storage.php | 1 + program/steps/mail/compose.inc | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/CHANGELOG b/CHANGELOG index ebc279622..8fd17b407 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,7 +8,7 @@ CHANGELOG Roundcube Webmail - Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836) - Fix empty email on identities list after identity update (#1488834) - Add new identities_level: (4) one identity with possibility to edit only signature -- Use Delivered-To header as a last resort for identity selection (#1488840) +- Use Delivered-To and Envelope-To headers for identity selection (#1488840, #1488553) - Fix XSS vulnerability using Flash files (#1488828) - Fix absolute positioning in HTML messages (#1488819) - Fix cache (in)validation after setting \Deleted flag diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index 245d911c0..7ec05b7af 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -65,6 +65,7 @@ abstract class rcube_storage 'MAIL-REPLY-TO', 'RETURN-PATH', 'DELIVERED-TO', + 'ENVELOPE-TO', ); const UNKNOWN = 0; diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index d181a72e6..d764f5289 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -455,6 +455,16 @@ function rcmail_identity_select($MESSAGE, $identities, $compose_mode) } } + // Fallback using Envelope-To + if ($from_idx === null && ($envelope_to = $MESSAGE->headers->others['envelope-to'])) { + foreach ($identities as $idx => $ident) { + if (in_array($ident['email_ascii'], (array)$envelope_to)) { + $from_idx = $idx; + break; + } + } + } + return $identities[$from_idx !== null ? $from_idx : $default_identity]; } -- cgit v1.2.3 From 0435f40999564586dd9bd9669696ec04c16f2e32 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 12 Dec 2012 13:32:24 +0100 Subject: Add EXISTS mode to count() method - return number of all messages in a folder, event if skip_deleted is enabled and/or search is active. --- program/lib/Roundcube/rcube_imap.php | 14 +++++++++----- program/lib/Roundcube/rcube_storage.php | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index a2495462a..ab90fa23f 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -571,7 +571,7 @@ class rcube_imap extends rcube_storage * Get message count for a specific folder * * @param string $folder Folder name - * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT] + * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS] * @param boolean $force Force reading from server and update cache * @param boolean $status Enables storing folder status info (max UID/count), * required for folder_status() @@ -592,7 +592,7 @@ class rcube_imap extends rcube_storage * protected method for getting nr of messages * * @param string $folder Folder name - * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT] + * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS] * @param boolean $force Force reading from server and update cache * @param boolean $status Enables storing folder status info (max UID/count), * required for folder_status() @@ -614,6 +614,10 @@ class rcube_imap extends rcube_storage } } + // EXISTS is a special alias for ALL, it allows to get the number + // of all messages in a folder also when search is active and with + // any skip_deleted setting + $a_folder_cache = $this->get_cache('messagecount'); // return cached value @@ -644,7 +648,7 @@ class rcube_imap extends rcube_storage $count = $this->conn->countRecent($folder); } // use SEARCH for message counting - else if (!empty($this->options['skip_deleted'])) { + else if ($mode != 'EXISTS' && !empty($this->options['skip_deleted'])) { $search_str = "ALL UNDELETED"; $keys = array('COUNT'); @@ -683,8 +687,8 @@ class rcube_imap extends rcube_storage } else { $count = $this->conn->countMessages($folder); - if ($status) { - $this->set_folder_stats($folder,'cnt', $count); + if ($status && $mode == 'ALL') { + $this->set_folder_stats($folder, 'cnt', $count); $this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0); } } diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index 7ec05b7af..763b9155e 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -354,7 +354,7 @@ abstract class rcube_storage * Get messages count for a specific folder. * * @param string $folder Folder name - * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT] + * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS] * @param boolean $force Force reading from server and update cache * @param boolean $status Enables storing folder status info (max UID/count), * required for folder_status() -- cgit v1.2.3 From 5b15700d11f7f1338039a436d5c911d297b4ac56 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 13 Dec 2012 14:41:36 +0100 Subject: Rename hook imap_connect to storage_connect --- program/lib/Roundcube/rcube_imap.php | 2 +- program/lib/Roundcube/rcube_plugin_api.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index a2495462a..a92def54d 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -151,7 +151,7 @@ class rcube_imap extends rcube_storage $attempt = 0; do { - $data = rcube::get_instance()->plugins->exec_hook('imap_connect', + $data = rcube::get_instance()->plugins->exec_hook('storage_connect', array_merge($this->options, array('host' => $host, 'user' => $user, 'attempt' => ++$attempt))); diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php index 51cf5d246..47508a2ef 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -78,7 +78,8 @@ class rcube_plugin_api 'identity_save' => 'identity_update', // to be removed after 0.8 'imap_init' => 'storage_init', - 'mailboxes_list' => 'storage_folders', + 'mailboxes_list' => 'storage_folders', + 'imap_connect' => 'storage_connect', ); /** -- cgit v1.2.3 From a072247dde60497f879b2a00790a6ec0a64fab4c Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sun, 16 Dec 2012 17:03:01 +0100 Subject: Fix package definitions and include framework classes in phpdoc --- bin/makedoc.sh | 5 +++-- program/include/bc.php | 1 + program/lib/Roundcube/rcube.php | 3 ++- program/lib/Roundcube/rcube_db.php | 2 +- program/lib/Roundcube/rcube_message_header.php | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) (limited to 'program/lib') diff --git a/bin/makedoc.sh b/bin/makedoc.sh index 40c75bf47..2a34254cb 100755 --- a/bin/makedoc.sh +++ b/bin/makedoc.sh @@ -1,10 +1,11 @@ #!/bin/sh -TITLE="Roundcube Classes" +TITLE="Roundcube Webmail" PACKAGES="Core" INSTALL_PATH="`dirname $0`/.." PATH_PROJECT=$INSTALL_PATH/program/include +PATH_FRAMEWORK=$INSTALL_PATH/program/lib/Roundcube PATH_DOCS=$INSTALL_PATH/doc/phpdoc BIN_PHPDOC="`/usr/bin/which phpdoc`" @@ -20,6 +21,6 @@ TEMPLATE=earthli PRIVATE=off # make documentation -$BIN_PHPDOC -d $PATH_PROJECT -t $PATH_DOCS -ti "$TITLE" -dn $PACKAGES \ +$BIN_PHPDOC -d $PATH_PROJECT,$PATH_FRAMEWORK -t $PATH_DOCS -ti "$TITLE" -dn $PACKAGES \ -o $OUTPUTFORMAT:$CONVERTER:$TEMPLATE -pp $PRIVATE diff --git a/program/include/bc.php b/program/include/bc.php index 12110c0ad..dc4d54fd7 100644 --- a/program/include/bc.php +++ b/program/include/bc.php @@ -23,6 +23,7 @@ * Roundcube Webmail deprecated functions * * @package Core + * @subpackage Legacy * @author Thomas Bruederli */ diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index cc4905a14..a127eeb4f 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -1266,7 +1266,8 @@ class rcube /** * Lightweight plugin API class serving as a dummy if plugins are not enabled * - * @package Core + * @package Framework + * @subpackage Core */ class rcube_dummy_plugin_api { diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 2c471e74d..e6e8c2ede 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -25,7 +25,7 @@ * This is a wrapper for the PHP PDO. * * @package Framework - * @sbpackage Database + * @subpackage Database */ class rcube_db { diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php index 7009a00af..16a0aaac9 100644 --- a/program/lib/Roundcube/rcube_message_header.php +++ b/program/lib/Roundcube/rcube_message_header.php @@ -257,7 +257,8 @@ class rcube_message_header /** * Class for sorting an array of rcube_message_header objects in a predetermined order. * - * @package Mail + * @package Framework + * @subpackage Storage * @author Aleksander Machniak */ class rcube_message_header_sorter -- cgit v1.2.3 From 9945f24274d84f6fe2124bdf23bc3bd2f128a6c9 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 18 Dec 2012 09:01:19 +0100 Subject: CS fixes --- program/lib/Roundcube/rcube_smtp.php | 764 +++++++++++++++++------------------ 1 file changed, 373 insertions(+), 391 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php index 96534c0b8..1a4958e2b 100644 --- a/program/lib/Roundcube/rcube_smtp.php +++ b/program/lib/Roundcube/rcube_smtp.php @@ -5,7 +5,7 @@ | program/include/rcube_smtp.php | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2010, The Roundcube Dev Team | + | Copyright (C) 2005-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -19,9 +19,6 @@ +-----------------------------------------------------------------------+ */ -// define headers delimiter -define('SMTP_MIME_CRLF', "\r\n"); - /** * Class to provide SMTP functionality using PEAR Net_SMTP * @@ -32,439 +29,424 @@ define('SMTP_MIME_CRLF', "\r\n"); */ class rcube_smtp { - - private $conn = null; - private $response; - private $error; - - - /** - * SMTP Connection and authentication - * - * @param string Server host - * @param string Server port - * @param string User name - * @param string Password - * - * @return bool Returns true on success, or false on error - */ - public function connect($host=null, $port=null, $user=null, $pass=null) - { - $rcube = rcube::get_instance(); - - // disconnect/destroy $this->conn - $this->disconnect(); - - // reset error/response var - $this->error = $this->response = null; - - // let plugins alter smtp connection config - $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array( - 'smtp_server' => $host ? $host : $rcube->config->get('smtp_server'), - 'smtp_port' => $port ? $port : $rcube->config->get('smtp_port', 25), - 'smtp_user' => $user ? $user : $rcube->config->get('smtp_user'), - 'smtp_pass' => $pass ? $pass : $rcube->config->get('smtp_pass'), - 'smtp_auth_cid' => $rcube->config->get('smtp_auth_cid'), - 'smtp_auth_pw' => $rcube->config->get('smtp_auth_pw'), - 'smtp_auth_type' => $rcube->config->get('smtp_auth_type'), - 'smtp_helo_host' => $rcube->config->get('smtp_helo_host'), - 'smtp_timeout' => $rcube->config->get('smtp_timeout'), - 'smtp_auth_callbacks' => array(), - )); - - $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']); - // when called from Installer it's possible to have empty $smtp_host here - if (!$smtp_host) $smtp_host = 'localhost'; - $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25; - $smtp_host_url = parse_url($smtp_host); - - // overwrite port - if (isset($smtp_host_url['host']) && isset($smtp_host_url['port'])) + private $conn = null; + private $response; + private $error; + + // define headers delimiter + const SMTP_MIME_CRLF = "\r\n"; + + + /** + * SMTP Connection and authentication + * + * @param string Server host + * @param string Server port + * @param string User name + * @param string Password + * + * @return bool Returns true on success, or false on error + */ + public function connect($host=null, $port=null, $user=null, $pass=null) { - $smtp_host = $smtp_host_url['host']; - $smtp_port = $smtp_host_url['port']; - } + $rcube = rcube::get_instance(); - // re-write smtp host - if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme'])) - $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']); + // disconnect/destroy $this->conn + $this->disconnect(); - // remove TLS prefix and set flag for use in Net_SMTP::auth() - if (preg_match('#^tls://#i', $smtp_host)) { - $smtp_host = preg_replace('#^tls://#i', '', $smtp_host); - $use_tls = true; - } + // reset error/response var + $this->error = $this->response = null; + + // let plugins alter smtp connection config + $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array( + 'smtp_server' => $host ? $host : $rcube->config->get('smtp_server'), + 'smtp_port' => $port ? $port : $rcube->config->get('smtp_port', 25), + 'smtp_user' => $user ? $user : $rcube->config->get('smtp_user'), + 'smtp_pass' => $pass ? $pass : $rcube->config->get('smtp_pass'), + 'smtp_auth_cid' => $rcube->config->get('smtp_auth_cid'), + 'smtp_auth_pw' => $rcube->config->get('smtp_auth_pw'), + 'smtp_auth_type' => $rcube->config->get('smtp_auth_type'), + 'smtp_helo_host' => $rcube->config->get('smtp_helo_host'), + 'smtp_timeout' => $rcube->config->get('smtp_timeout'), + 'smtp_auth_callbacks' => array(), + )); + + $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']); + // when called from Installer it's possible to have empty $smtp_host here + if (!$smtp_host) $smtp_host = 'localhost'; + $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25; + $smtp_host_url = parse_url($smtp_host); + + // overwrite port + if (isset($smtp_host_url['host']) && isset($smtp_host_url['port'])) { + $smtp_host = $smtp_host_url['host']; + $smtp_port = $smtp_host_url['port']; + } - if (!empty($CONFIG['smtp_helo_host'])) - $helo_host = $CONFIG['smtp_helo_host']; - else if (!empty($_SERVER['SERVER_NAME'])) - $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']); - else - $helo_host = 'localhost'; + // re-write smtp host + if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme'])) { + $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']); + } - // IDNA Support - $smtp_host = rcube_utils::idn_to_ascii($smtp_host); + // remove TLS prefix and set flag for use in Net_SMTP::auth() + if (preg_match('#^tls://#i', $smtp_host)) { + $smtp_host = preg_replace('#^tls://#i', '', $smtp_host); + $use_tls = true; + } - $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host); + if (!empty($CONFIG['smtp_helo_host'])) { + $helo_host = $CONFIG['smtp_helo_host']; + } + else if (!empty($_SERVER['SERVER_NAME'])) { + $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']); + } + else { + $helo_host = 'localhost'; + } - if ($rcube->config->get('smtp_debug')) - $this->conn->setDebug(true, array($this, 'debug_handler')); + // IDNA Support + $smtp_host = rcube_utils::idn_to_ascii($smtp_host); - // register authentication methods - if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) { - foreach ($CONFIG['smtp_auth_callbacks'] as $callback) { - $this->conn->setAuthMethod($callback['name'], $callback['function'], - isset($callback['prepend']) ? $callback['prepend'] : true); - } - } + $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host); - // try to connect to server and exit on failure - $result = $this->conn->connect($smtp_timeout); + if ($rcube->config->get('smtp_debug')) { + $this->conn->setDebug(true, array($this, 'debug_handler')); + } - if (PEAR::isError($result)) { - $this->response[] = "Connection failed: ".$result->getMessage(); - $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code)); - $this->conn = null; - return false; - } + // register authentication methods + if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) { + foreach ($CONFIG['smtp_auth_callbacks'] as $callback) { + $this->conn->setAuthMethod($callback['name'], $callback['function'], + isset($callback['prepend']) ? $callback['prepend'] : true); + } + } - // workaround for timeout bug in Net_SMTP 1.5.[0-1] (#1487843) - if (method_exists($this->conn, 'setTimeout') - && ($timeout = ini_get('default_socket_timeout')) - ) { - $this->conn->setTimeout($timeout); - } + // try to connect to server and exit on failure + $result = $this->conn->connect($smtp_timeout); - $smtp_user = str_replace('%u', $rcube->get_user_name(), $CONFIG['smtp_user']); - $smtp_pass = str_replace('%p', $rcube->get_user_password(), $CONFIG['smtp_pass']); - $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type']; + if (PEAR::isError($result)) { + $this->response[] = "Connection failed: ".$result->getMessage(); + $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code)); + $this->conn = null; + return false; + } - if (!empty($CONFIG['smtp_auth_cid'])) { - $smtp_authz = $smtp_user; - $smtp_user = $CONFIG['smtp_auth_cid']; - $smtp_pass = $CONFIG['smtp_auth_pw']; - } + // workaround for timeout bug in Net_SMTP 1.5.[0-1] (#1487843) + if (method_exists($this->conn, 'setTimeout') + && ($timeout = ini_get('default_socket_timeout')) + ) { + $this->conn->setTimeout($timeout); + } - // attempt to authenticate to the SMTP server - if ($smtp_user && $smtp_pass) - { - // IDNA Support - if (strpos($smtp_user, '@')) { - $smtp_user = rcube_utils::idn_to_ascii($smtp_user); - } - - $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz); - - if (PEAR::isError($result)) - { - $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code)); - $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')'; - $this->reset(); - $this->disconnect(); - return false; - } - } + $smtp_user = str_replace('%u', $rcube->get_user_name(), $CONFIG['smtp_user']); + $smtp_pass = str_replace('%p', $rcube->get_user_password(), $CONFIG['smtp_pass']); + $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type']; - return true; - } - - - /** - * Function for sending mail - * - * @param string Sender e-Mail address - * - * @param mixed Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of recipients, - * each RFC822 valid. This may contain recipients not - * specified in the headers, for Bcc:, resending - * messages, etc. - * @param mixed The message headers to send with the mail - * Either as an associative array or a finally - * formatted string - * @param mixed The full text of the message body, including any Mime parts - * or file handle - * @param array Delivery options (e.g. DSN request) - * - * @return bool Returns true on success, or false on error - */ - public function send_mail($from, $recipients, &$headers, &$body, $opts=null) - { - if (!is_object($this->conn)) - return false; - - // prepare message headers as string - if (is_array($headers)) - { - if (!($headerElements = $this->_prepare_headers($headers))) { - $this->reset(); - return false; - } + if (!empty($CONFIG['smtp_auth_cid'])) { + $smtp_authz = $smtp_user; + $smtp_user = $CONFIG['smtp_auth_cid']; + $smtp_pass = $CONFIG['smtp_auth_pw']; + } - list($from, $text_headers) = $headerElements; - } - else if (is_string($headers)) - $text_headers = $headers; - else - { - $this->reset(); - $this->response[] = "Invalid message headers"; - return false; + // attempt to authenticate to the SMTP server + if ($smtp_user && $smtp_pass) { + // IDNA Support + if (strpos($smtp_user, '@')) { + $smtp_user = rcube_utils::idn_to_ascii($smtp_user); + } + + $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz); + + if (PEAR::isError($result)) { + $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code)); + $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')'; + $this->reset(); + $this->disconnect(); + return false; + } + } + + return true; } - // exit if no from address is given - if (!isset($from)) + /** + * Function for sending mail + * + * @param string Sender e-Mail address + * + * @param mixed Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. This may contain recipients not + * specified in the headers, for Bcc:, resending + * messages, etc. + * @param mixed The message headers to send with the mail + * Either as an associative array or a finally + * formatted string + * @param mixed The full text of the message body, including any Mime parts + * or file handle + * @param array Delivery options (e.g. DSN request) + * + * @return bool Returns true on success, or false on error + */ + public function send_mail($from, $recipients, &$headers, &$body, $opts=null) { - $this->reset(); - $this->response[] = "No From address has been provided"; - return false; - } + if (!is_object($this->conn)) { + return false; + } - // RFC3461: Delivery Status Notification - if ($opts['dsn']) { - $exts = $this->conn->getServiceExtensions(); + // prepare message headers as string + if (is_array($headers)) { + if (!($headerElements = $this->_prepare_headers($headers))) { + $this->reset(); + return false; + } - if (isset($exts['DSN'])) { - $from_params = 'RET=HDRS'; - $recipient_params = 'NOTIFY=SUCCESS,FAILURE'; - } - } + list($from, $text_headers) = $headerElements; + } + else if (is_string($headers)) { + $text_headers = $headers; + } + else { + $this->reset(); + $this->response[] = "Invalid message headers"; + return false; + } + + // exit if no from address is given + if (!isset($from)) { + $this->reset(); + $this->response[] = "No From address has been provided"; + return false; + } + + // RFC3461: Delivery Status Notification + if ($opts['dsn']) { + $exts = $this->conn->getServiceExtensions(); + + if (isset($exts['DSN'])) { + $from_params = 'RET=HDRS'; + $recipient_params = 'NOTIFY=SUCCESS,FAILURE'; + } + } + + // RFC2298.3: remove envelope sender address + if (preg_match('/Content-Type: multipart\/report/', $text_headers) + && preg_match('/report-type=disposition-notification/', $text_headers) + ) { + $from = ''; + } + + // set From: address + if (PEAR::isError($this->conn->mailFrom($from, $from_params))) { + $err = $this->conn->getResponse(); + $this->error = array('label' => 'smtpfromerror', 'vars' => array( + 'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1])); + $this->response[] = "Failed to set sender '$from'"; + $this->reset(); + return false; + } + + // prepare list of recipients + $recipients = $this->_parse_rfc822($recipients); + if (PEAR::isError($recipients)) { + $this->error = array('label' => 'smtprecipientserror'); + $this->reset(); + return false; + } - // RFC2298.3: remove envelope sender address - if (preg_match('/Content-Type: multipart\/report/', $text_headers) - && preg_match('/report-type=disposition-notification/', $text_headers) - ) { - $from = ''; + // set mail recipients + foreach ($recipients as $recipient) { + if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) { + $err = $this->conn->getResponse(); + $this->error = array('label' => 'smtptoerror', 'vars' => array( + 'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1])); + $this->response[] = "Failed to add recipient '$recipient'"; + $this->reset(); + return false; + } + } + + if (is_resource($body)) { + // file handle + $data = $body; + $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers); + } + else { + // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data + // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy. + // We are still forced to make another copy here for a couple ticks so we don't really + // get to save a copy in the method call. + $data = $text_headers . "\r\n" . $body; + + // unset old vars to save data and so we can pass into SMTP_CONN->data by reference. + unset($text_headers, $body); + } + + // Send the message's headers and the body as SMTP data. + if (PEAR::isError($result = $this->conn->data($data, $text_headers))) { + $err = $this->conn->getResponse(); + if (!in_array($err[0], array(354, 250, 221))) { + $msg = sprintf('[%d] %s', $err[0], $err[1]); + } + else { + $msg = $result->getMessage(); + } + + $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg)); + $this->response[] = "Failed to send data"; + $this->reset(); + return false; + } + + $this->response[] = join(': ', $this->conn->getResponse()); + return true; } - // set From: address - if (PEAR::isError($this->conn->mailFrom($from, $from_params))) + /** + * Reset the global SMTP connection + */ + public function reset() { - $err = $this->conn->getResponse(); - $this->error = array('label' => 'smtpfromerror', 'vars' => array( - 'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1])); - $this->response[] = "Failed to set sender '$from'"; - $this->reset(); - return false; + if (is_object($this->conn)) { + $this->conn->rset(); + } } - // prepare list of recipients - $recipients = $this->_parse_rfc822($recipients); - if (PEAR::isError($recipients)) + /** + * Disconnect the global SMTP connection + */ + public function disconnect() { - $this->error = array('label' => 'smtprecipientserror'); - $this->reset(); - return false; + if (is_object($this->conn)) { + $this->conn->disconnect(); + $this->conn = null; + } } - // set mail recipients - foreach ($recipients as $recipient) + + /** + * This is our own debug handler for the SMTP connection + */ + public function debug_handler(&$smtp, $message) { - if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) { - $err = $this->conn->getResponse(); - $this->error = array('label' => 'smtptoerror', 'vars' => array( - 'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1])); - $this->response[] = "Failed to add recipient '$recipient'"; - $this->reset(); - return false; - } + rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message)); } - if (is_resource($body)) + /** + * Get error message + */ + public function get_error() { - // file handle - $data = $body; - $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers); - } else { - // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data - // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy. - // We are still forced to make another copy here for a couple ticks so we don't really - // get to save a copy in the method call. - $data = $text_headers . "\r\n" . $body; - - // unset old vars to save data and so we can pass into SMTP_CONN->data by reference. - unset($text_headers, $body); + return $this->error; } - // Send the message's headers and the body as SMTP data. - if (PEAR::isError($result = $this->conn->data($data, $text_headers))) + /** + * Get server response messages array + */ + public function get_response() { - $err = $this->conn->getResponse(); - if (!in_array($err[0], array(354, 250, 221))) - $msg = sprintf('[%d] %s', $err[0], $err[1]); - else - $msg = $result->getMessage(); - - $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg)); - $this->response[] = "Failed to send data"; - $this->reset(); - return false; + return $this->response; } - $this->response[] = join(': ', $this->conn->getResponse()); - return true; - } - - - /** - * Reset the global SMTP connection - * @access public - */ - public function reset() - { - if (is_object($this->conn)) - $this->conn->rset(); - } - - - /** - * Disconnect the global SMTP connection - * @access public - */ - public function disconnect() - { - if (is_object($this->conn)) { - $this->conn->disconnect(); - $this->conn = null; - } - } - - - /** - * This is our own debug handler for the SMTP connection - * @access public - */ - public function debug_handler(&$smtp, $message) - { - rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message)); - } - - - /** - * Get error message - * @access public - */ - public function get_error() - { - return $this->error; - } - - - /** - * Get server response messages array - * @access public - */ - public function get_response() - { - return $this->response; - } - - - /** - * Take an array of mail headers and return a string containing - * text usable in sending a message. - * - * @param array $headers The array of headers to prepare, in an associative - * array, where the array key is the header name (ie, - * 'Subject'), and the array value is the header - * value (ie, 'test'). The header produced from those - * values would be 'Subject: test'. - * - * @return mixed Returns false if it encounters a bad address, - * otherwise returns an array containing two - * elements: Any From: address found in the headers, - * and the plain text version of the headers. - * @access private - */ - private function _prepare_headers($headers) - { - $lines = array(); - $from = null; - - foreach ($headers as $key => $value) + /** + * Take an array of mail headers and return a string containing + * text usable in sending a message. + * + * @param array $headers The array of headers to prepare, in an associative + * array, where the array key is the header name (ie, + * 'Subject'), and the array value is the header + * value (ie, 'test'). The header produced from those + * values would be 'Subject: test'. + * + * @return mixed Returns false if it encounters a bad address, + * otherwise returns an array containing two + * elements: Any From: address found in the headers, + * and the plain text version of the headers. + */ + private function _prepare_headers($headers) { - if (strcasecmp($key, 'From') === 0) - { - $addresses = $this->_parse_rfc822($value); - - if (is_array($addresses)) - $from = $addresses[0]; - - // Reject envelope From: addresses with spaces. - if (strpos($from, ' ') !== false) - return false; - - $lines[] = $key . ': ' . $value; - } - else if (strcasecmp($key, 'Received') === 0) - { - $received = array(); - if (is_array($value)) - { - foreach ($value as $line) - $received[] = $key . ': ' . $line; - } - else - { - $received[] = $key . ': ' . $value; + $lines = array(); + $from = null; + + foreach ($headers as $key => $value) { + if (strcasecmp($key, 'From') === 0) { + $addresses = $this->_parse_rfc822($value); + + if (is_array($addresses)) { + $from = $addresses[0]; + } + + // Reject envelope From: addresses with spaces. + if (strpos($from, ' ') !== false) { + return false; + } + + $lines[] = $key . ': ' . $value; + } + else if (strcasecmp($key, 'Received') === 0) { + $received = array(); + if (is_array($value)) { + foreach ($value as $line) { + $received[] = $key . ': ' . $line; + } + } + else { + $received[] = $key . ': ' . $value; + } + + // Put Received: headers at the top. Spam detectors often + // flag messages with Received: headers after the Subject: + // as spam. + $lines = array_merge($received, $lines); + } + else { + // If $value is an array (i.e., a list of addresses), convert + // it to a comma-delimited string of its elements (addresses). + if (is_array($value)) { + $value = implode(', ', $value); + } + + $lines[] = $key . ': ' . $value; + } } - // Put Received: headers at the top. Spam detectors often - // flag messages with Received: headers after the Subject: - // as spam. - $lines = array_merge($received, $lines); - } - else - { - // If $value is an array (i.e., a list of addresses), convert - // it to a comma-delimited string of its elements (addresses). - if (is_array($value)) - $value = implode(', ', $value); - - $lines[] = $key . ': ' . $value; - } + return array($from, join(self::SMTP_MIME_CRLF, $lines) . self::SMTP_MIME_CRLF); } - return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF); - } - - /** - * Take a set of recipients and parse them, returning an array of - * bare addresses (forward paths) that can be passed to sendmail - * or an smtp server with the rcpt to: command. - * - * @param mixed Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of recipients, - * each RFC822 valid. - * - * @return array An array of forward paths (bare addresses). - * @access private - */ - private function _parse_rfc822($recipients) - { - // if we're passed an array, assume addresses are valid and implode them before parsing. - if (is_array($recipients)) - $recipients = implode(', ', $recipients); - - $addresses = array(); - $recipients = rcube_utils::explode_quoted_string(',', $recipients); - - reset($recipients); - while (list($k, $recipient) = each($recipients)) + /** + * Take a set of recipients and parse them, returning an array of + * bare addresses (forward paths) that can be passed to sendmail + * or an smtp server with the rcpt to: command. + * + * @param mixed Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. + * + * @return array An array of forward paths (bare addresses). + */ + private function _parse_rfc822($recipients) { - $a = rcube_utils::explode_quoted_string(' ', $recipient); - while (list($k2, $word) = each($a)) - { - if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"') - { - $word = preg_replace('/^<|>$/', '', trim($word)); - if (in_array($word, $addresses)===false) - array_push($addresses, $word); + // if we're passed an array, assume addresses are valid and implode them before parsing. + if (is_array($recipients)) { + $recipients = implode(', ', $recipients); } - } - } - return $addresses; - } + $addresses = array(); + $recipients = rcube_utils::explode_quoted_string(',', $recipients); + + reset($recipients); + while (list($k, $recipient) = each($recipients)) { + $a = rcube_utils::explode_quoted_string(' ', $recipient); + while (list($k2, $word) = each($a)) { + if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"') { + $word = preg_replace('/^<|>$/', '', trim($word)); + if (in_array($word, $addresses) === false) { + array_push($addresses, $word); + } + } + } + } + return $addresses; + } } -- cgit v1.2.3 From d2534c63f2c0b640a39fce2a71b14a5dcda6e7fd Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 18 Dec 2012 09:07:00 +0100 Subject: Cleanup, remove file paths from doc --- program/lib/Roundcube/bootstrap.php | 3 --- program/lib/Roundcube/html.php | 3 --- program/lib/Roundcube/rcube.php | 2 -- program/lib/Roundcube/rcube_addressbook.php | 3 --- program/lib/Roundcube/rcube_base_replacer.php | 3 --- program/lib/Roundcube/rcube_browser.php | 3 --- program/lib/Roundcube/rcube_cache.php | 3 --- program/lib/Roundcube/rcube_charset.php | 3 --- program/lib/Roundcube/rcube_config.php | 3 --- program/lib/Roundcube/rcube_contacts.php | 3 --- program/lib/Roundcube/rcube_content_filter.php | 3 --- program/lib/Roundcube/rcube_csv2vcard.php | 2 -- program/lib/Roundcube/rcube_db.php | 4 ---- program/lib/Roundcube/rcube_db_mssql.php | 4 ---- program/lib/Roundcube/rcube_db_mysql.php | 4 ---- program/lib/Roundcube/rcube_db_pgsql.php | 4 ---- program/lib/Roundcube/rcube_db_sqlite.php | 4 ---- program/lib/Roundcube/rcube_db_sqlsrv.php | 4 ---- program/lib/Roundcube/rcube_enriched.php | 4 ---- program/lib/Roundcube/rcube_image.php | 3 --- program/lib/Roundcube/rcube_imap.php | 4 ---- program/lib/Roundcube/rcube_imap_cache.php | 4 ---- program/lib/Roundcube/rcube_imap_generic.php | 4 ---- program/lib/Roundcube/rcube_ldap.php | 4 ---- program/lib/Roundcube/rcube_message.php | 3 --- program/lib/Roundcube/rcube_message_header.php | 3 --- program/lib/Roundcube/rcube_message_part.php | 4 ---- program/lib/Roundcube/rcube_mime.php | 4 ---- program/lib/Roundcube/rcube_output.php | 4 +--- program/lib/Roundcube/rcube_plugin.php | 2 -- program/lib/Roundcube/rcube_plugin_api.php | 3 --- program/lib/Roundcube/rcube_result_index.php | 4 ---- program/lib/Roundcube/rcube_result_set.php | 4 ---- program/lib/Roundcube/rcube_result_thread.php | 4 ---- program/lib/Roundcube/rcube_session.php | 3 --- program/lib/Roundcube/rcube_smtp.php | 3 --- program/lib/Roundcube/rcube_spellchecker.php | 4 ---- program/lib/Roundcube/rcube_storage.php | 4 ---- program/lib/Roundcube/rcube_string_replacer.php | 4 ---- program/lib/Roundcube/rcube_user.php | 4 ---- program/lib/Roundcube/rcube_utils.php | 3 --- program/lib/Roundcube/rcube_vcard.php | 3 --- 42 files changed, 1 insertion(+), 143 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php index eed7db8c1..18c07ddab 100644 --- a/program/lib/Roundcube/bootstrap.php +++ b/program/lib/Roundcube/bootstrap.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/bootstrap.php | - | | | This file is part of the Roundcube PHP suite | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | CONTENTS: | | Roundcube Framework Initialization | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index 5fb574b97..33b766c44 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/html.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2011, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | Helper class to create valid XHTML code | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index a127eeb4f..cde549052 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php index ea8df700c..a8f274a8f 100644 --- a/program/lib/Roundcube/rcube_addressbook.php +++ b/program/lib/Roundcube/rcube_addressbook.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_addressbook.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2006-2012, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | Interface to the local address book database | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_base_replacer.php b/program/lib/Roundcube/rcube_base_replacer.php index b2a0fc13c..fcd85c2c8 100644 --- a/program/lib/Roundcube/rcube_base_replacer.php +++ b/program/lib/Roundcube/rcube_base_replacer.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_base_replacer.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | Provide basic functions for base URL replacement | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_browser.php b/program/lib/Roundcube/rcube_browser.php index 154e7ef4e..d10fe2a2c 100644 --- a/program/lib/Roundcube/rcube_browser.php +++ b/program/lib/Roundcube/rcube_browser.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_browser.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2007-2009, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | Class representing the client browser's properties | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_cache.php b/program/lib/Roundcube/rcube_cache.php index 3e1ce4fc8..92f12a8bf 100644 --- a/program/lib/Roundcube/rcube_cache.php +++ b/program/lib/Roundcube/rcube_cache.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_cache.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2011, The Roundcube Dev Team | | Copyright (C) 2011, Kolab Systems AG | @@ -14,7 +12,6 @@ | | | PURPOSE: | | Caching engine | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | diff --git a/program/lib/Roundcube/rcube_charset.php b/program/lib/Roundcube/rcube_charset.php index 6135a5711..968d1c4b8 100644 --- a/program/lib/Roundcube/rcube_charset.php +++ b/program/lib/Roundcube/rcube_charset.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_charset.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -15,7 +13,6 @@ | | | PURPOSE: | | Provide charset conversion functionality | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php index 615faf3ad..2190dc4c2 100644 --- a/program/lib/Roundcube/rcube_config.php +++ b/program/lib/Roundcube/rcube_config.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_config.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2012, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | Class to read configuration settings | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php index 5b4292a4c..a98b13865 100644 --- a/program/lib/Roundcube/rcube_contacts.php +++ b/program/lib/Roundcube/rcube_contacts.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_contacts.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2006-2012, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | Interface to the local address book database | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_content_filter.php b/program/lib/Roundcube/rcube_content_filter.php index 99916a300..b814bb71d 100644 --- a/program/lib/Roundcube/rcube_content_filter.php +++ b/program/lib/Roundcube/rcube_content_filter.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_content_filter.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2011, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | PHP stream filter to detect evil content in mail attachments | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php index 850c0c4c3..9c28a3b49 100644 --- a/program/lib/Roundcube/rcube_csv2vcard.php +++ b/program/lib/Roundcube/rcube_csv2vcard.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_csv2vcard.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2012, The Roundcube Dev Team | | | diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index e6e8c2ede..47ddc81a6 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -2,8 +2,6 @@ /** +-----------------------------------------------------------------------+ - | program/include/rcube_db.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -13,13 +11,11 @@ | | | PURPOSE: | | Database wrapper class that implements PHP PDO functions | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Database independent query interface. * This is a wrapper for the PHP PDO. diff --git a/program/lib/Roundcube/rcube_db_mssql.php b/program/lib/Roundcube/rcube_db_mssql.php index c95663c74..84fe22bbc 100644 --- a/program/lib/Roundcube/rcube_db_mssql.php +++ b/program/lib/Roundcube/rcube_db_mssql.php @@ -2,8 +2,6 @@ /** +-----------------------------------------------------------------------+ - | program/include/rcube_db_mssql.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -14,13 +12,11 @@ | PURPOSE: | | Database wrapper class that implements PHP PDO functions | | for MS SQL Server database | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Database independent query interface * This is a wrapper for the PHP PDO diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php index 1c5ba1de7..c32cc259c 100644 --- a/program/lib/Roundcube/rcube_db_mysql.php +++ b/program/lib/Roundcube/rcube_db_mysql.php @@ -2,8 +2,6 @@ /** +-----------------------------------------------------------------------+ - | program/include/rcube_db_mysql.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -14,13 +12,11 @@ | PURPOSE: | | Database wrapper class that implements PHP PDO functions | | for MySQL database | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Database independent query interface * diff --git a/program/lib/Roundcube/rcube_db_pgsql.php b/program/lib/Roundcube/rcube_db_pgsql.php index 797860a84..cf23c5e48 100644 --- a/program/lib/Roundcube/rcube_db_pgsql.php +++ b/program/lib/Roundcube/rcube_db_pgsql.php @@ -2,8 +2,6 @@ /** +-----------------------------------------------------------------------+ - | program/include/rcube_db_pgsql.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -14,13 +12,11 @@ | PURPOSE: | | Database wrapper class that implements PHP PDO functions | | for PostgreSQL database | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Database independent query interface * This is a wrapper for the PHP PDO diff --git a/program/lib/Roundcube/rcube_db_sqlite.php b/program/lib/Roundcube/rcube_db_sqlite.php index 65dcb6d6e..326c6a710 100644 --- a/program/lib/Roundcube/rcube_db_sqlite.php +++ b/program/lib/Roundcube/rcube_db_sqlite.php @@ -2,8 +2,6 @@ /** +-----------------------------------------------------------------------+ - | program/include/rcube_db_sqlite.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -14,13 +12,11 @@ | PURPOSE: | | Database wrapper class that implements PHP PDO functions | | for SQLite database | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Database independent query interface * This is a wrapper for the PHP PDO diff --git a/program/lib/Roundcube/rcube_db_sqlsrv.php b/program/lib/Roundcube/rcube_db_sqlsrv.php index 8b6ffe807..e69678025 100644 --- a/program/lib/Roundcube/rcube_db_sqlsrv.php +++ b/program/lib/Roundcube/rcube_db_sqlsrv.php @@ -2,8 +2,6 @@ /** +-----------------------------------------------------------------------+ - | program/include/rcube_db_sqlsrv.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -14,13 +12,11 @@ | PURPOSE: | | Database wrapper class that implements PHP PDO functions | | for MS SQL Server database | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Database independent query interface * This is a wrapper for the PHP PDO diff --git a/program/lib/Roundcube/rcube_enriched.php b/program/lib/Roundcube/rcube_enriched.php index 8b64fe054..8c628c912 100644 --- a/program/lib/Roundcube/rcube_enriched.php +++ b/program/lib/Roundcube/rcube_enriched.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_enriched.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -13,14 +11,12 @@ | | | PURPOSE: | | Helper class to convert Enriched to HTML format (RFC 1523, 1896) | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Ryo Chijiiwa (IlohaMail) | +-----------------------------------------------------------------------+ */ - /** * Class for Enriched to HTML conversion * diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php index b72a24c51..ad96842d2 100644 --- a/program/lib/Roundcube/rcube_image.php +++ b/program/lib/Roundcube/rcube_image.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_image.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -14,7 +12,6 @@ | | | PURPOSE: | | Image resizer and converter | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index ea3743d02..74c1f5324 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_imap.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -14,14 +12,12 @@ | | | PURPOSE: | | IMAP Storage Engine | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Interface class for accessing an IMAP server * diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php index 31214cfbf..f33ac076c 100644 --- a/program/lib/Roundcube/rcube_imap_cache.php +++ b/program/lib/Roundcube/rcube_imap_cache.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_imap_cache.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -13,14 +11,12 @@ | | | PURPOSE: | | Caching of IMAP folder contents (messages and index) | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Interface class for accessing Roundcube messages cache * diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 28d56c16f..112e91350 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2,8 +2,6 @@ /** +-----------------------------------------------------------------------+ - | program/include/rcube_imap_generic.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -19,14 +17,12 @@ | functionality built-in. | | | | Based on Iloha IMAP Library. See http://ilohamail.org/ for details | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Ryo Chijiiwa | +-----------------------------------------------------------------------+ */ - /** * PHP based wrapper class to connect to an IMAP server * diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index c32cea728..d4bc669fd 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_ldap.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2006-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -14,7 +12,6 @@ | | | PURPOSE: | | Interface to an LDAP address directory | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Andreas Dick | @@ -22,7 +19,6 @@ +-----------------------------------------------------------------------+ */ - /** * Model class to access an LDAP address directory * diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index c626af08a..f41493d12 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_message.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2010, The Roundcube Dev Team | | | @@ -19,7 +17,6 @@ +-----------------------------------------------------------------------+ */ - /** * Logical representation of a mail message with all its data * and related functions diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php index 16a0aaac9..274ae7f9f 100644 --- a/program/lib/Roundcube/rcube_message_header.php +++ b/program/lib/Roundcube/rcube_message_header.php @@ -2,8 +2,6 @@ /** +-----------------------------------------------------------------------+ - | program/include/rcube_message_header.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -14,7 +12,6 @@ | | | PURPOSE: | | E-mail message headers representation | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_message_part.php b/program/lib/Roundcube/rcube_message_part.php index c9c9257eb..4222ba390 100644 --- a/program/lib/Roundcube/rcube_message_part.php +++ b/program/lib/Roundcube/rcube_message_part.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_message_part.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -14,14 +12,12 @@ | | | PURPOSE: | | Class representing a message part | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Class representing a message part * diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index 4bb5b483f..eef8ca17c 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_mime.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -14,14 +12,12 @@ | | | PURPOSE: | | MIME message parsing utilities | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Class for parsing MIME messages * diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php index 4ef42f598..b8ae86cf6 100644 --- a/program/lib/Roundcube/rcube_output.php +++ b/program/lib/Roundcube/rcube_output.php @@ -2,17 +2,15 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_output.php | - | | | This file is part of the Roundcube PHP suite | | Copyright (C) 2005-2012 The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | | See the README file for a full license statement. | + | | | CONTENTS: | | Abstract class for output generation | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php index 5db85025d..06247d456 100644 --- a/program/lib/Roundcube/rcube_plugin.php +++ b/program/lib/Roundcube/rcube_plugin.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_plugin.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2009, The Roundcube Dev Team | | | diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php index 47508a2ef..b4626441d 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_plugin_api.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2011, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | Plugins repository | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_result_index.php b/program/lib/Roundcube/rcube_result_index.php index 4d1ae13b6..5f592c54f 100644 --- a/program/lib/Roundcube/rcube_result_index.php +++ b/program/lib/Roundcube/rcube_result_index.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_result_index.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2011, The Roundcube Dev Team | | Copyright (C) 2011, Kolab Systems AG | @@ -14,14 +12,12 @@ | | | PURPOSE: | | SORT/SEARCH/ESEARCH response handler | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Class for accessing IMAP's SORT/SEARCH/ESEARCH result * diff --git a/program/lib/Roundcube/rcube_result_set.php b/program/lib/Roundcube/rcube_result_set.php index 456d1c9d6..1391e5e4b 100644 --- a/program/lib/Roundcube/rcube_result_set.php +++ b/program/lib/Roundcube/rcube_result_set.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_result_set.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2006-2011, The Roundcube Dev Team | | | @@ -13,13 +11,11 @@ | | | PURPOSE: | | Class representing an address directory result set | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ */ - /** * Roundcube result set class. * Representing an address directory result set. diff --git a/program/lib/Roundcube/rcube_result_thread.php b/program/lib/Roundcube/rcube_result_thread.php index c609bdc39..7657550be 100644 --- a/program/lib/Roundcube/rcube_result_thread.php +++ b/program/lib/Roundcube/rcube_result_thread.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_result_thread.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2011, The Roundcube Dev Team | | Copyright (C) 2011, Kolab Systems AG | @@ -14,14 +12,12 @@ | | | PURPOSE: | | THREAD response handler | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Class for accessing IMAP's THREAD result * diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php index fdbf668ca..69eaabedc 100644 --- a/program/lib/Roundcube/rcube_session.php +++ b/program/lib/Roundcube/rcube_session.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_session.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2011, Kolab Systems AG | @@ -14,7 +12,6 @@ | | | PURPOSE: | | Provide database supported session management | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php index 1a4958e2b..79ffcfbf8 100644 --- a/program/lib/Roundcube/rcube_smtp.php +++ b/program/lib/Roundcube/rcube_smtp.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_smtp.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -13,7 +11,6 @@ | | | PURPOSE: | | Provide SMTP functionality using socket connections | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php index fce2cac75..e9e1ceb0f 100644 --- a/program/lib/Roundcube/rcube_spellchecker.php +++ b/program/lib/Roundcube/rcube_spellchecker.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_spellchecker.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2011, Kolab Systems AG | | Copyright (C) 2008-2011, The Roundcube Dev Team | @@ -14,14 +12,12 @@ | | | PURPOSE: | | Spellchecking using different backends | - | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ */ - /** * Helper class for spellchecking with Googielspell and PSpell support. * diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index 763b9155e..65de2660c 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_storage.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | Copyright (C) 2012, Kolab Systems AG | @@ -14,14 +12,12 @@ | | | PURPOSE: | | Mail Storage Engine | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Abstract class for accessing mail messages storage server * diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php index 584b9f68c..68288f54c 100644 --- a/program/lib/Roundcube/rcube_string_replacer.php +++ b/program/lib/Roundcube/rcube_string_replacer.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_string_replacer.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2009-2012, The Roundcube Dev Team | | | @@ -13,13 +11,11 @@ | | | PURPOSE: | | Handle string replacements based on preg_replace_callback | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ */ - /** * Helper class for string replacements based on preg_replace_callback * diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php index f6b77f5e1..505b190d1 100644 --- a/program/lib/Roundcube/rcube_user.php +++ b/program/lib/Roundcube/rcube_user.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_user.inc | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -14,14 +12,12 @@ | PURPOSE: | | This class represents a system user linked and provides access | | to the related database records. | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ - /** * Class representing a system user * diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index 500f2c371..4b687111e 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_utils.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2012, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | @@ -20,7 +18,6 @@ +-----------------------------------------------------------------------+ */ - /** * Utility class providing common functions * diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index 45ee601e5..a5c5ccec8 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -2,8 +2,6 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_vcard.php | - | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2008-2012, The Roundcube Dev Team | | | @@ -19,7 +17,6 @@ +-----------------------------------------------------------------------+ */ - /** * Logical representation of a vcard-based address record * Provides functions to parse and export vCard data format -- cgit v1.2.3 From 8cacecb2ff8b2c819f573bbd47f6bc8171d26ee8 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 18 Dec 2012 09:30:15 +0100 Subject: CS fixes --- program/lib/Roundcube/rcube_vcard.php | 1463 +++++++++++++++++---------------- 1 file changed, 758 insertions(+), 705 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index a5c5ccec8..e6fa5b248 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -26,765 +26,818 @@ */ class rcube_vcard { - private static $values_decoded = false; - private $raw = array( - 'FN' => array(), - 'N' => array(array('','','','','')), - ); - private static $fieldmap = array( - 'phone' => 'TEL', - 'birthday' => 'BDAY', - 'website' => 'URL', - 'notes' => 'NOTE', - 'email' => 'EMAIL', - 'address' => 'ADR', - 'jobtitle' => 'TITLE', - 'department' => 'X-DEPARTMENT', - 'gender' => 'X-GENDER', - 'maidenname' => 'X-MAIDENNAME', - 'anniversary' => 'X-ANNIVERSARY', - 'assistant' => 'X-ASSISTANT', - 'manager' => 'X-MANAGER', - 'spouse' => 'X-SPOUSE', - 'edit' => 'X-AB-EDIT', - ); - private $typemap = array('IPHONE' => 'mobile', 'CELL' => 'mobile', 'WORK,FAX' => 'workfax'); - private $phonetypemap = array('HOME1' => 'HOME', 'BUSINESS1' => 'WORK', 'BUSINESS2' => 'WORK2', 'BUSINESSFAX' => 'WORK,FAX', 'MOBILE' => 'CELL'); - private $addresstypemap = array('BUSINESS' => 'WORK'); - private $immap = array('X-JABBER' => 'jabber', 'X-ICQ' => 'icq', 'X-MSN' => 'msn', 'X-AIM' => 'aim', 'X-YAHOO' => 'yahoo', 'X-SKYPE' => 'skype', 'X-SKYPE-USERNAME' => 'skype'); - - public $business = false; - public $displayname; - public $surname; - public $firstname; - public $middlename; - public $nickname; - public $organization; - public $email = array(); - - public static $eol = "\r\n"; - - /** - * Constructor - */ - public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array()) - { - if (!empty($fielmap)) - $this->extend_fieldmap($fieldmap); - - if (!empty($vcard)) - $this->load($vcard, $charset, $detect); - } - - - /** - * Load record from (internal, unfolded) vcard 3.0 format - * - * @param string vCard string to parse - * @param string Charset of string values - * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required - */ - public function load($vcard, $charset = RCUBE_CHARSET, $detect = false) - { - self::$values_decoded = false; - $this->raw = self::vcard_decode($vcard); - - // resolve charset parameters - if ($charset == null) { - $this->raw = self::charset_convert($this->raw); + private static $values_decoded = false; + private $raw = array( + 'FN' => array(), + 'N' => array(array('','','','','')), + ); + private static $fieldmap = array( + 'phone' => 'TEL', + 'birthday' => 'BDAY', + 'website' => 'URL', + 'notes' => 'NOTE', + 'email' => 'EMAIL', + 'address' => 'ADR', + 'jobtitle' => 'TITLE', + 'department' => 'X-DEPARTMENT', + 'gender' => 'X-GENDER', + 'maidenname' => 'X-MAIDENNAME', + 'anniversary' => 'X-ANNIVERSARY', + 'assistant' => 'X-ASSISTANT', + 'manager' => 'X-MANAGER', + 'spouse' => 'X-SPOUSE', + 'edit' => 'X-AB-EDIT', + ); + private $typemap = array( + 'IPHONE' => 'mobile', + 'CELL' => 'mobile', + 'WORK,FAX' => 'workfax', + ); + private $phonetypemap = array( + 'HOME1' => 'HOME', + 'BUSINESS1' => 'WORK', + 'BUSINESS2' => 'WORK2', + 'BUSINESSFAX' => 'WORK,FAX', + 'MOBILE' => 'CELL', + ); + private $addresstypemap = array( + 'BUSINESS' => 'WORK', + ); + private $immap = array( + 'X-JABBER' => 'jabber', + 'X-ICQ' => 'icq', + 'X-MSN' => 'msn', + 'X-AIM' => 'aim', + 'X-YAHOO' => 'yahoo', + 'X-SKYPE' => 'skype', + 'X-SKYPE-USERNAME' => 'skype', + ); + + public $business = false; + public $displayname; + public $surname; + public $firstname; + public $middlename; + public $nickname; + public $organization; + public $email = array(); + + public static $eol = "\r\n"; + + + /** + * Constructor + */ + public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array()) + { + if (!empty($fielmap)) { + $this->extend_fieldmap($fieldmap); + } + + if (!empty($vcard)) { + $this->load($vcard, $charset, $detect); + } } - // vcard has encoded values and charset should be detected - else if ($detect && self::$values_decoded && - ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw))) && $detected_charset != RCUBE_CHARSET) { - $this->raw = self::charset_convert($this->raw, $detected_charset); + + /** + * Load record from (internal, unfolded) vcard 3.0 format + * + * @param string vCard string to parse + * @param string Charset of string values + * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required + */ + public function load($vcard, $charset = RCUBE_CHARSET, $detect = false) + { + self::$values_decoded = false; + $this->raw = self::vcard_decode($vcard); + + // resolve charset parameters + if ($charset == null) { + $this->raw = self::charset_convert($this->raw); + } + // vcard has encoded values and charset should be detected + else if ($detect && self::$values_decoded + && ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw))) + && $detected_charset != RCUBE_CHARSET + ) { + $this->raw = self::charset_convert($this->raw, $detected_charset); + } + + // consider FN empty if the same as the primary e-mail address + if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0]) { + $this->raw['FN'][0][0] = ''; + } + + // find well-known address fields + $this->displayname = $this->raw['FN'][0][0]; + $this->surname = $this->raw['N'][0][0]; + $this->firstname = $this->raw['N'][0][1]; + $this->middlename = $this->raw['N'][0][2]; + $this->nickname = $this->raw['NICKNAME'][0][0]; + $this->organization = $this->raw['ORG'][0][0]; + $this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization)); + + foreach ((array)$this->raw['EMAIL'] as $i => $raw_email) { + $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email; + } + + // make the pref e-mail address the first entry in $this->email + $pref_index = $this->get_type_index('EMAIL', 'pref'); + if ($pref_index > 0) { + $tmp = $this->email[0]; + $this->email[0] = $this->email[$pref_index]; + $this->email[$pref_index] = $tmp; + } } - // consider FN empty if the same as the primary e-mail address - if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0]) - $this->raw['FN'][0][0] = ''; - - // find well-known address fields - $this->displayname = $this->raw['FN'][0][0]; - $this->surname = $this->raw['N'][0][0]; - $this->firstname = $this->raw['N'][0][1]; - $this->middlename = $this->raw['N'][0][2]; - $this->nickname = $this->raw['NICKNAME'][0][0]; - $this->organization = $this->raw['ORG'][0][0]; - $this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization)); - - foreach ((array)$this->raw['EMAIL'] as $i => $raw_email) - $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email; - - // make the pref e-mail address the first entry in $this->email - $pref_index = $this->get_type_index('EMAIL', 'pref'); - if ($pref_index > 0) { - $tmp = $this->email[0]; - $this->email[0] = $this->email[$pref_index]; - $this->email[$pref_index] = $tmp; + /** + * Return vCard data as associative array to be unsed in Roundcube address books + * + * @return array Hash array with key-value pairs + */ + public function get_assoc() + { + $out = array('name' => $this->displayname); + $typemap = $this->typemap; + + // copy name fields to output array + foreach (array('firstname','surname','middlename','nickname','organization') as $col) { + if (strlen($this->$col)) { + $out[$col] = $this->$col; + } + } + + if ($this->raw['N'][0][3]) + $out['prefix'] = $this->raw['N'][0][3]; + if ($this->raw['N'][0][4]) + $out['suffix'] = $this->raw['N'][0][4]; + + // convert from raw vcard data into associative data for Roundcube + foreach (array_flip(self::$fieldmap) as $tag => $col) { + foreach ((array)$this->raw[$tag] as $i => $raw) { + if (is_array($raw)) { + $k = -1; + $key = $col; + $subtype = ''; + + if (!empty($raw['type'])) { + $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true)); + $combined = strtoupper($combined); + + if ($typemap[$combined]) { + $subtype = $typemap[$combined]; + } + else if ($typemap[$raw['type'][++$k]]) { + $subtype = $typemap[$raw['type'][$k]]; + } + else { + $subtype = strtolower($raw['type'][$k]); + } + + while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref')) { + $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]); + } + } + + // read vcard 2.1 subtype + if (!$subtype) { + foreach ($raw as $k => $v) { + if (!is_numeric($k) && $v === true && ($k = strtolower($k)) + && !in_array($k, array('pref','internet','voice','base64')) + ) { + $k_uc = strtoupper($k); + $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k; + break; + } + } + } + + // force subtype if none set + if (!$subtype && preg_match('/^(email|phone|address|website)/', $key)) { + $subtype = 'other'; + } + + if ($subtype) { + $key .= ':' . $subtype; + } + + // split ADR values into assoc array + if ($tag == 'ADR') { + list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw; + $out[$key][] = $value; + } + else { + $out[$key][] = $raw[0]; + } + } + else { + $out[$col][] = $raw; + } + } + } + + // handle special IM fields as used by Apple + foreach ($this->immap as $tag => $type) { + foreach ((array)$this->raw[$tag] as $i => $raw) { + $out['im:'.$type][] = $raw[0]; + } + } + + // copy photo data + if ($this->raw['PHOTO']) { + $out['photo'] = $this->raw['PHOTO'][0][0]; + } + + return $out; } - } - - - /** - * Return vCard data as associative array to be unsed in Roundcube address books - * - * @return array Hash array with key-value pairs - */ - public function get_assoc() - { - $out = array('name' => $this->displayname); - $typemap = $this->typemap; - - // copy name fields to output array - foreach (array('firstname','surname','middlename','nickname','organization') as $col) { - if (strlen($this->$col)) - $out[$col] = $this->$col; + + /** + * Convert the data structure into a vcard 3.0 string + */ + public function export($folded = true) + { + $vcard = self::vcard_encode($this->raw); + return $folded ? self::rfc2425_fold($vcard) : $vcard; } - if ($this->raw['N'][0][3]) - $out['prefix'] = $this->raw['N'][0][3]; - if ($this->raw['N'][0][4]) - $out['suffix'] = $this->raw['N'][0][4]; - - // convert from raw vcard data into associative data for Roundcube - foreach (array_flip(self::$fieldmap) as $tag => $col) { - foreach ((array)$this->raw[$tag] as $i => $raw) { - if (is_array($raw)) { - $k = -1; - $key = $col; - $subtype = ''; - - if (!empty($raw['type'])) { - $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true)); - $combined = strtoupper($combined); - - if ($typemap[$combined]) { - $subtype = $typemap[$combined]; - } - else if ($typemap[$raw['type'][++$k]]) { - $subtype = $typemap[$raw['type'][$k]]; + /** + * Clear the given fields in the loaded vcard data + * + * @param array List of field names to be reset + */ + public function reset($fields = null) + { + if (!$fields) { + $fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap), + array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY')); + } + + foreach ($fields as $f) { + unset($this->raw[$f]); + } + + if (!$this->raw['N']) { + $this->raw['N'] = array(array('','','','','')); + } + if (!$this->raw['FN']) { + $this->raw['FN'] = array(); + } + + $this->email = array(); + } + + /** + * Setter for address record fields + * + * @param string Field name + * @param string Field value + * @param string Type/section name + */ + public function set($field, $value, $type = 'HOME') + { + $field = strtolower($field); + $type_uc = strtoupper($type); + + switch ($field) { + case 'name': + case 'displayname': + $this->raw['FN'][0][0] = $this->displayname = $value; + break; + + case 'surname': + $this->raw['N'][0][0] = $this->surname = $value; + break; + + case 'firstname': + $this->raw['N'][0][1] = $this->firstname = $value; + break; + + case 'middlename': + $this->raw['N'][0][2] = $this->middlename = $value; + break; + + case 'prefix': + $this->raw['N'][0][3] = $value; + break; + + case 'suffix': + $this->raw['N'][0][4] = $value; + break; + + case 'nickname': + $this->raw['NICKNAME'][0][0] = $this->nickname = $value; + break; + + case 'organization': + $this->raw['ORG'][0][0] = $this->organization = $value; + break; + + case 'photo': + if (strpos($value, 'http:') === 0) { + // TODO: fetch file from URL and save it locally? + $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true); } else { - $subtype = strtolower($raw['type'][$k]); + $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value)); + } + break; + + case 'email': + $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc))); + $this->email[] = $value; + break; + + case 'im': + // save IM subtypes into extension fields + $typemap = array_flip($this->immap); + if ($field = $typemap[strtolower($type)]) { + $this->raw[$field][] = array(0 => $value); } + break; - while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref')) - $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]); - } - - // read vcard 2.1 subtype - if (!$subtype) { - foreach ($raw as $k => $v) { - if (!is_numeric($k) && $v === true && ($k = strtolower($k)) - && !in_array($k, array('pref','internet','voice','base64')) - ) { - $k_uc = strtoupper($k); - $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k; - break; - } + case 'birthday': + case 'anniversary': + if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) { + $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date')); } - } + break; - // force subtype if none set - if (!$subtype && preg_match('/^(email|phone|address|website)/', $key)) - $subtype = 'other'; + case 'address': + if ($this->addresstypemap[$type_uc]) { + $type = $this->addresstypemap[$type_uc]; + } - if ($subtype) - $key .= ':' . $subtype; + $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']); - // split ADR values into assoc array - if ($tag == 'ADR') { - list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw; - $out[$key][] = $value; - } - else - $out[$key][] = $raw[0]; - } - else { - $out[$col][] = $raw; + // fall through if not empty + if (!strlen(join('', $value))) { + break; + } + + default: + if ($field == 'phone' && $this->phonetypemap[$type_uc]) { + $type = $this->phonetypemap[$type_uc]; + } + + if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) { + $index = count($this->raw[$tag]); + $this->raw[$tag][$index] = (array)$value; + if ($type) { + $typemap = array_flip($this->typemap); + $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type)); + } + } + break; } - } } - // handle special IM fields as used by Apple - foreach ($this->immap as $tag => $type) { - foreach ((array)$this->raw[$tag] as $i => $raw) { - $out['im:'.$type][] = $raw[0]; - } + /** + * Setter for individual vcard properties + * + * @param string VCard tag name + * @param array Value-set of this vcard property + * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set + */ + public function set_raw($tag, $value, $append = false) + { + $index = $append ? count($this->raw[$tag]) : 0; + $this->raw[$tag][$index] = (array)$value; } - // copy photo data - if ($this->raw['PHOTO']) - $out['photo'] = $this->raw['PHOTO'][0][0]; - - return $out; - } - - - /** - * Convert the data structure into a vcard 3.0 string - */ - public function export($folded = true) - { - $vcard = self::vcard_encode($this->raw); - return $folded ? self::rfc2425_fold($vcard) : $vcard; - } - - - /** - * Clear the given fields in the loaded vcard data - * - * @param array List of field names to be reset - */ - public function reset($fields = null) - { - if (!$fields) - $fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap), array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY')); - - foreach ($fields as $f) - unset($this->raw[$f]); - - if (!$this->raw['N']) - $this->raw['N'] = array(array('','','','','')); - if (!$this->raw['FN']) - $this->raw['FN'] = array(); - - $this->email = array(); - } - - - /** - * Setter for address record fields - * - * @param string Field name - * @param string Field value - * @param string Type/section name - */ - public function set($field, $value, $type = 'HOME') - { - $field = strtolower($field); - $type_uc = strtoupper($type); - - switch ($field) { - case 'name': - case 'displayname': - $this->raw['FN'][0][0] = $this->displayname = $value; - break; - - case 'surname': - $this->raw['N'][0][0] = $this->surname = $value; - break; - - case 'firstname': - $this->raw['N'][0][1] = $this->firstname = $value; - break; - - case 'middlename': - $this->raw['N'][0][2] = $this->middlename = $value; - break; - - case 'prefix': - $this->raw['N'][0][3] = $value; - break; - - case 'suffix': - $this->raw['N'][0][4] = $value; - break; - - case 'nickname': - $this->raw['NICKNAME'][0][0] = $this->nickname = $value; - break; - - case 'organization': - $this->raw['ORG'][0][0] = $this->organization = $value; - break; - - case 'photo': - if (strpos($value, 'http:') === 0) { - // TODO: fetch file from URL and save it locally? - $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true); - } - else { - $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value)); - } - break; - - case 'email': - $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc))); - $this->email[] = $value; - break; - - case 'im': - // save IM subtypes into extension fields - $typemap = array_flip($this->immap); - if ($field = $typemap[strtolower($type)]) - $this->raw[$field][] = array(0 => $value); - break; - - case 'birthday': - case 'anniversary': - if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) - $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date')); - break; - - case 'address': - if ($this->addresstypemap[$type_uc]) - $type = $this->addresstypemap[$type_uc]; - - $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']); - - // fall through if not empty - if (!strlen(join('', $value))) - break; - - default: - if ($field == 'phone' && $this->phonetypemap[$type_uc]) - $type = $this->phonetypemap[$type_uc]; - - if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) { - $index = count($this->raw[$tag]); - $this->raw[$tag][$index] = (array)$value; - if ($type) { - $typemap = array_flip($this->typemap); - $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type)); - } - } - break; - } - } - - /** - * Setter for individual vcard properties - * - * @param string VCard tag name - * @param array Value-set of this vcard property - * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set - */ - public function set_raw($tag, $value, $append = false) - { - $index = $append ? count($this->raw[$tag]) : 0; - $this->raw[$tag][$index] = (array)$value; - } - - - /** - * Find index with the '$type' attribute - * - * @param string Field name - * @return int Field index having $type set - */ - private function get_type_index($field, $type = 'pref') - { - $result = 0; - if ($this->raw[$field]) { - foreach ($this->raw[$field] as $i => $data) { - if (is_array($data['type']) && in_array_nocase('pref', $data['type'])) - $result = $i; - } + /** + * Find index with the '$type' attribute + * + * @param string Field name + * @return int Field index having $type set + */ + private function get_type_index($field, $type = 'pref') + { + $result = 0; + if ($this->raw[$field]) { + foreach ($this->raw[$field] as $i => $data) { + if (is_array($data['type']) && in_array_nocase('pref', $data['type'])) { + $result = $i; + } + } + } + + return $result; } - return $result; - } - - - /** - * Convert a whole vcard (array) to UTF-8. - * If $force_charset is null, each member value that has a charset parameter will be converted - */ - private static function charset_convert($card, $force_charset = null) - { - foreach ($card as $key => $node) { - foreach ($node as $i => $subnode) { - if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) { - foreach ($subnode as $j => $value) { - if (is_numeric($j) && is_string($value)) - $card[$key][$i][$j] = rcube_charset::convert($value, $charset); - } - unset($card[$key][$i]['charset']); - } - } + /** + * Convert a whole vcard (array) to UTF-8. + * If $force_charset is null, each member value that has a charset parameter will be converted + */ + private static function charset_convert($card, $force_charset = null) + { + foreach ($card as $key => $node) { + foreach ($node as $i => $subnode) { + if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) { + foreach ($subnode as $j => $value) { + if (is_numeric($j) && is_string($value)) { + $card[$key][$i][$j] = rcube_charset::convert($value, $charset); + } + } + unset($card[$key][$i]['charset']); + } + } + } + + return $card; } - return $card; - } - - - /** - * Extends fieldmap definition - */ - public function extend_fieldmap($map) - { - if (is_array($map)) - self::$fieldmap = array_merge($map, self::$fieldmap); - } - - - /** - * Factory method to import a vcard file - * - * @param string vCard file content - * @return array List of rcube_vcard objects - */ - public static function import($data) - { - $out = array(); - - // check if charsets are specified (usually vcard version < 3.0 but this is not reliable) - if (preg_match('/charset=/i', substr($data, 0, 2048))) - $charset = null; - // detect charset and convert to utf-8 - else if (($charset = self::detect_encoding($data)) && $charset != RCUBE_CHARSET) { - $data = rcube_charset::convert($data, $charset); - $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM - $charset = RCUBE_CHARSET; + /** + * Extends fieldmap definition + */ + public function extend_fieldmap($map) + { + if (is_array($map)) { + self::$fieldmap = array_merge($map, self::$fieldmap); + } } - $vcard_block = ''; - $in_vcard_block = false; + /** + * Factory method to import a vcard file + * + * @param string vCard file content + * + * @return array List of rcube_vcard objects + */ + public static function import($data) + { + $out = array(); + + // check if charsets are specified (usually vcard version < 3.0 but this is not reliable) + if (preg_match('/charset=/i', substr($data, 0, 2048))) { + $charset = null; + } + // detect charset and convert to utf-8 + else if (($charset = self::detect_encoding($data)) && $charset != RCUBE_CHARSET) { + $data = rcube_charset::convert($data, $charset); + $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM + $charset = RCUBE_CHARSET; + } - foreach (preg_split("/[\r\n]+/", $data) as $i => $line) { - if ($in_vcard_block && !empty($line)) - $vcard_block .= $line . "\n"; + $vcard_block = ''; + $in_vcard_block = false; - $line = trim($line); + foreach (preg_split("/[\r\n]+/", $data) as $i => $line) { + if ($in_vcard_block && !empty($line)) { + $vcard_block .= $line . "\n"; + } - if (preg_match('/^END:VCARD$/i', $line)) { - // parse vcard - $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap); - if (!empty($obj->displayname) || !empty($obj->email)) - $out[] = $obj; + $line = trim($line); - $in_vcard_block = false; - } - else if (preg_match('/^BEGIN:VCARD$/i', $line)) { - $vcard_block = $line . "\n"; - $in_vcard_block = true; - } + if (preg_match('/^END:VCARD$/i', $line)) { + // parse vcard + $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap); + if (!empty($obj->displayname) || !empty($obj->email)) { + $out[] = $obj; + } + + $in_vcard_block = false; + } + else if (preg_match('/^BEGIN:VCARD$/i', $line)) { + $vcard_block = $line . "\n"; + $in_vcard_block = true; + } + } + + return $out; } - return $out; - } - - - /** - * Normalize vcard data for better parsing - * - * @param string vCard block - * @return string Cleaned vcard block - */ - private static function cleanup($vcard) - { - // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;) - $vcard = preg_replace( - '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', - '\2;type=\5\3:\4', - $vcard); - - // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility - $vcard = preg_replace_callback( - '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', - array('self', 'x_abrelatednames_callback'), - $vcard); - - // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines - $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard); - - // convert X-WAB-GENDER to X-GENDER - if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) { - $value = $matches[1] == '2' ? 'male' : 'female'; - $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard); + /** + * Normalize vcard data for better parsing + * + * @param string vCard block + * + * @return string Cleaned vcard block + */ + private static function cleanup($vcard) + { + // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;) + $vcard = preg_replace( + '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', + '\2;type=\5\3:\4', + $vcard); + + // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility + $vcard = preg_replace_callback( + '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', + array('self', 'x_abrelatednames_callback'), + $vcard); + + // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines + $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard); + + // convert X-WAB-GENDER to X-GENDER + if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) { + $value = $matches[1] == '2' ? 'male' : 'female'; + $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard); + } + + // if N doesn't have any semicolons, add some + $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard); + + return $vcard; } - // if N doesn't have any semicolons, add some - $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard); - - return $vcard; - } - - private static function x_abrelatednames_callback($matches) - { - return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4]; - } - - private static function rfc2425_fold_callback($matches) - { - // chunk_split string and avoid lines breaking multibyte characters - $c = 71; - $out .= substr($matches[1], 0, $c); - for ($n = $c; $c < strlen($matches[1]); $c++) { - // break if length > 75 or mutlibyte character starts after position 71 - if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) { - $out .= "\r\n "; - $n = 0; - } - $out .= $matches[1][$c]; - $n++; + private static function x_abrelatednames_callback($matches) + { + return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4]; } - return $out; - } - - public static function rfc2425_fold($val) - { - return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val); - } - - - /** - * Decodes a vcard block (vcard 3.0 format, unfolded) - * into an array structure - * - * @param string vCard block to parse - * @return array Raw data structure - */ - private static function vcard_decode($vcard) - { - // Perform RFC2425 line unfolding and split lines - $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard); - $lines = explode("\n", $vcard); - $data = array(); - - for ($i=0; $i < count($lines); $i++) { - if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line)) - continue; - - if (preg_match('/^(BEGIN|END)$/i', $line[1])) - continue; - - // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:" - if (($data['VERSION'][0] == "2.1") && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2) && !preg_match('/^TYPE=/i', $regs2[2])) { - $line[1] = $regs2[1]; - foreach (explode(';', $regs2[2]) as $prop) - $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop); - } - - if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) { - $entry = array(); - $field = strtoupper($regs2[1][0]); - $enc = null; - - foreach($regs2[1] as $attrid => $attr) { - if ((list($key, $value) = explode('=', $attr)) && $value) { - $value = trim($value); - if ($key == 'ENCODING') { - $value = strtoupper($value); - // add next line(s) to value string if QP line end detected - if ($value == 'QUOTED-PRINTABLE') { - while (preg_match('/=$/', $lines[$i])) - $line[2] .= "\n" . $lines[++$i]; - } - $enc = $value; + private static function rfc2425_fold_callback($matches) + { + // chunk_split string and avoid lines breaking multibyte characters + $c = 71; + $out .= substr($matches[1], 0, $c); + for ($n = $c; $c < strlen($matches[1]); $c++) { + // break if length > 75 or mutlibyte character starts after position 71 + if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) { + $out .= "\r\n "; + $n = 0; } - else { - $lc_key = strtolower($key); - $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ',')); - } - } - else if ($attrid > 0) { - $entry[strtolower($key)] = true; // true means attr without =value - } + $out .= $matches[1][$c]; + $n++; } - // decode value - if ($enc || !empty($entry['base64'])) { - // save encoding type (#1488432) - if ($enc == 'B') { - $entry['encoding'] = 'B'; - // should we use vCard 3.0 instead? - // $entry['base64'] = true; - } - $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64'); - } + return $out; + } + + public static function rfc2425_fold($val) + { + return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val); + } - if ($enc != 'B' && empty($entry['base64'])) { - $line[2] = self::vcard_unquote($line[2]); + /** + * Decodes a vcard block (vcard 3.0 format, unfolded) + * into an array structure + * + * @param string vCard block to parse + * + * @return array Raw data structure + */ + private static function vcard_decode($vcard) + { + // Perform RFC2425 line unfolding and split lines + $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard); + $lines = explode("\n", $vcard); + $data = array(); + + for ($i=0; $i < count($lines); $i++) { + if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line)) + continue; + + if (preg_match('/^(BEGIN|END)$/i', $line[1])) + continue; + + // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:" + if ($data['VERSION'][0] == "2.1" + && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2) + && !preg_match('/^TYPE=/i', $regs2[2]) + ) { + $line[1] = $regs2[1]; + foreach (explode(';', $regs2[2]) as $prop) { + $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop); + } + } + + if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) { + $entry = array(); + $field = strtoupper($regs2[1][0]); + $enc = null; + + foreach($regs2[1] as $attrid => $attr) { + if ((list($key, $value) = explode('=', $attr)) && $value) { + $value = trim($value); + if ($key == 'ENCODING') { + $value = strtoupper($value); + // add next line(s) to value string if QP line end detected + if ($value == 'QUOTED-PRINTABLE') { + while (preg_match('/=$/', $lines[$i])) { + $line[2] .= "\n" . $lines[++$i]; + } + } + $enc = $value; + } + else { + $lc_key = strtolower($key); + $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ',')); + } + } + else if ($attrid > 0) { + $entry[strtolower($key)] = true; // true means attr without =value + } + } + + // decode value + if ($enc || !empty($entry['base64'])) { + // save encoding type (#1488432) + if ($enc == 'B') { + $entry['encoding'] = 'B'; + // should we use vCard 3.0 instead? + // $entry['base64'] = true; + } + $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64'); + } + + if ($enc != 'B' && empty($entry['base64'])) { + $line[2] = self::vcard_unquote($line[2]); + } + + $entry = array_merge($entry, (array) $line[2]); + $data[$field][] = $entry; + } } - $entry = array_merge($entry, (array) $line[2]); - $data[$field][] = $entry; - } + unset($data['VERSION']); + return $data; } - unset($data['VERSION']); - return $data; - } - - - /** - * Decode a given string with the encoding rule from ENCODING attributes - * - * @param string String to decode - * @param string Encoding type (quoted-printable and base64 supported) - * @return string Decoded 8bit value - */ - private static function decode_value($value, $encoding) - { - switch (strtolower($encoding)) { - case 'quoted-printable': - self::$values_decoded = true; - return quoted_printable_decode($value); - - case 'base64': - case 'b': - self::$values_decoded = true; - return base64_decode($value); - - default: - return $value; + /** + * Decode a given string with the encoding rule from ENCODING attributes + * + * @param string String to decode + * @param string Encoding type (quoted-printable and base64 supported) + * + * @return string Decoded 8bit value + */ + private static function decode_value($value, $encoding) + { + switch (strtolower($encoding)) { + case 'quoted-printable': + self::$values_decoded = true; + return quoted_printable_decode($value); + + case 'base64': + case 'b': + self::$values_decoded = true; + return base64_decode($value); + + default: + return $value; + } } - } - - - /** - * Encodes an entry for storage in our database (vcard 3.0 format, unfolded) - * - * @param array Raw data structure to encode - * @return string vCard encoded string - */ - static function vcard_encode($data) - { - foreach((array)$data as $type => $entries) { - /* valid N has 5 properties */ - while ($type == "N" && is_array($entries[0]) && count($entries[0]) < 5) - $entries[0][] = ""; - - // make sure FN is not empty (required by RFC2426) - if ($type == "FN" && empty($entries)) - $entries[0] = $data['EMAIL'][0][0]; - - foreach((array)$entries as $entry) { - $attr = ''; - if (is_array($entry)) { - $value = array(); - foreach($entry as $attrname => $attrvalues) { - if (is_int($attrname)) { - if (!empty($entry['base64']) || $entry['encoding'] == 'B') { - $attrvalues = base64_encode($attrvalues); - } - $value[] = $attrvalues; + + /** + * Encodes an entry for storage in our database (vcard 3.0 format, unfolded) + * + * @param array Raw data structure to encode + * + * @return string vCard encoded string + */ + static function vcard_encode($data) + { + foreach ((array)$data as $type => $entries) { + // valid N has 5 properties + while ($type == "N" && is_array($entries[0]) && count($entries[0]) < 5) { + $entries[0][] = ""; } - else if (is_bool($attrvalues)) { - if ($attrvalues) { - $attr .= strtoupper(";$attrname"); // true means just tag, not tag=value, as in PHOTO;BASE64:... - } + + // make sure FN is not empty (required by RFC2426) + if ($type == "FN" && empty($entries)) { + $entries[0] = $data['EMAIL'][0][0]; } - else { - foreach((array)$attrvalues as $attrvalue) - $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ','); + + foreach ((array)$entries as $entry) { + $attr = ''; + if (is_array($entry)) { + $value = array(); + foreach ($entry as $attrname => $attrvalues) { + if (is_int($attrname)) { + if (!empty($entry['base64']) || $entry['encoding'] == 'B') { + $attrvalues = base64_encode($attrvalues); + } + $value[] = $attrvalues; + } + else if (is_bool($attrvalues)) { + // true means just tag, not tag=value, as in PHOTO;BASE64:... + if ($attrvalues) { + $attr .= strtoupper(";$attrname"); + } + } + else { + foreach((array)$attrvalues as $attrvalue) { + $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ','); + } + } + } + } + else { + $value = $entry; + } + + // skip empty entries + if (self::is_empty($value)) { + continue; + } + + $vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . self::$eol; } - } } - else { - $value = $entry; - } - - // skip empty entries - if (self::is_empty($value)) - continue; - $vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . self::$eol; - } + return 'BEGIN:VCARD' . self::$eol . 'VERSION:3.0' . self::$eol . $vcard . 'END:VCARD'; } - return 'BEGIN:VCARD' . self::$eol . 'VERSION:3.0' . self::$eol . $vcard . 'END:VCARD'; - } - - - /** - * Join indexed data array to a vcard quoted string - * - * @param array Field data - * @param string Separator - * @return string Joined and quoted string - */ - private static function vcard_quote($s, $sep = ';') - { - if (is_array($s)) { - foreach($s as $part) { - $r[] = self::vcard_quote($part, $sep); - } - return(implode($sep, (array)$r)); - } - else { - return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;')); - } - } - - - /** - * Split quoted string - * - * @param string vCard string to split - * @param string Separator char/string - * @return array List with splited values - */ - private static function vcard_unquote($s, $sep = ';') - { - // break string into parts separated by $sep, but leave escaped $sep alone - if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) { - foreach($parts as $s) { - $result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep); - } - return $result; + /** + * Join indexed data array to a vcard quoted string + * + * @param array Field data + * @param string Separator + * + * @return string Joined and quoted string + */ + private static function vcard_quote($s, $sep = ';') + { + if (is_array($s)) { + foreach($s as $part) { + $r[] = self::vcard_quote($part, $sep); + } + return(implode($sep, (array)$r)); + } + + return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;')); } - else { - return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':')); + + /** + * Split quoted string + * + * @param string vCard string to split + * @param string Separator char/string + * + * @return array List with splited values + */ + private static function vcard_unquote($s, $sep = ';') + { + // break string into parts separated by $sep, but leave escaped $sep alone + if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) { + foreach($parts as $s) { + $result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep); + } + return $result; + } + + return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':')); } - } - - - /** - * Check if vCard entry is empty: empty string or an array with - * all entries empty. - * - * @param mixed $value Attribute value (string or array) - * - * @return bool True if the value is empty, False otherwise - */ - private static function is_empty($value) - { - foreach ((array)$value as $v) { - if (((string)$v) !== '') { - return false; - } + + /** + * Check if vCard entry is empty: empty string or an array with + * all entries empty. + * + * @param mixed $value Attribute value (string or array) + * + * @return bool True if the value is empty, False otherwise + */ + private static function is_empty($value) + { + foreach ((array)$value as $v) { + if (((string)$v) !== '') { + return false; + } + } + + return true; } - return true; - } - - /** - * Extract array values by a filter - * - * @param array Array to filter - * @param keys Array or comma separated list of values to keep - * @param boolean Invert key selection: remove the listed values - * @return array The filtered array - */ - private static function array_filter($arr, $values, $inverse = false) - { - if (!is_array($values)) - $values = explode(',', $values); - - $result = array(); - $keep = array_flip((array)$values); - foreach ($arr as $key => $val) - if ($inverse != isset($keep[strtolower($val)])) - $result[$key] = $val; - - return $result; - } - - /** - * Returns UNICODE type based on BOM (Byte Order Mark) - * - * @param string Input string to test - * @return string Detected encoding - */ - private static function detect_encoding($string) - { - $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1 - - return rcube_charset::detect($string, $fallback); - } + /** + * Extract array values by a filter + * + * @param array Array to filter + * @param keys Array or comma separated list of values to keep + * @param boolean Invert key selection: remove the listed values + * + * @return array The filtered array + */ + private static function array_filter($arr, $values, $inverse = false) + { + if (!is_array($values)) { + $values = explode(',', $values); + } + + $result = array(); + $keep = array_flip((array)$values); + foreach ($arr as $key => $val) { + if ($inverse != isset($keep[strtolower($val)])) { + $result[$key] = $val; + } + } + + return $result; + } + + /** + * Returns UNICODE type based on BOM (Byte Order Mark) + * + * @param string Input string to test + * + * @return string Detected encoding + */ + private static function detect_encoding($string) + { + $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1 + + return rcube_charset::detect($string, $fallback); + } } -- cgit v1.2.3 From 83370e5ff14f55f6af435807713956160f91abfa Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 18 Dec 2012 12:54:38 +0100 Subject: Display 'Sender' header in message preview --- CHANGELOG | 1 + program/lib/Roundcube/rcube_storage.php | 1 + program/localization/en_US/labels.inc | 1 + program/steps/mail/func.inc | 10 +++++++++- 4 files changed, 12 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/CHANGELOG b/CHANGELOG index a89e02930..8cfeaf89d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Display 'Sender' header in message preview - Plugin API: Added message_before_send hook - Fix contact copy/add-to-group operations on search result (#1488862) - Use matching identity in MDN response (#1488864) diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index 65de2660c..8a36f1f9d 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -53,6 +53,7 @@ abstract class rcube_storage protected $all_headers = array( 'IN-REPLY-TO', 'BCC', + 'SENDER', 'MESSAGE-ID', 'CONTENT-TRANSFER-ENCODING', 'REFERENCES', diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index abb0dca5d..730e6af09 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -41,6 +41,7 @@ $labels['junk'] = 'Junk'; // message listing $labels['subject'] = 'Subject'; $labels['from'] = 'From'; +$labels['sender'] = 'Sender'; $labels['to'] = 'To'; $labels['cc'] = 'Cc'; $labels['bcc'] = 'Bcc'; diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 88391b102..f5165399b 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -967,7 +967,7 @@ function rcmail_message_headers($attrib, $headers=null) } // show these headers - $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto', + $standard_headers = array('subject', 'from', 'sender', 'to', 'cc', 'bcc', 'replyto', 'mail-reply-to', 'mail-followup-to', 'date', 'priority'); $exclude_headers = $attrib['exclude'] ? explode(',', $attrib['exclude']) : array(); $output_headers = array(); @@ -1018,6 +1018,14 @@ function rcmail_message_headers($attrib, $headers=null) else continue; } + else if ($hkey == 'sender') { + if ($headers['sender'] != $headers['from']) { + $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title); + $ishtml = true; + } + else + continue; + } else if ($hkey == 'mail-followup-to') { $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title); $ishtml = true; -- cgit v1.2.3 From 0d214498d04439c29c9764fa291dcfde49701467 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 18 Dec 2012 19:02:53 +0100 Subject: CS fixes --- program/lib/Roundcube/html.php | 19 +- program/lib/Roundcube/rcube_addressbook.php | 4 +- program/lib/Roundcube/rcube_session.php | 1159 ++++++++++++----------- program/lib/Roundcube/rcube_string_replacer.php | 298 +++--- 4 files changed, 754 insertions(+), 726 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index 33b766c44..522a82305 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -169,7 +169,7 @@ class html $attr = array('href' => $attr); } return self::tag('a', $attr, $cont, array_merge(self::$common_attrib, - array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup'))); + array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup'))); } /** @@ -675,7 +675,7 @@ class html_table extends html } $cell = new stdClass; - $cell->attrib = $attr; + $cell->attrib = $attr; $cell->content = $cont; $this->rows[$this->rowindex]->cells[$this->colindex] = $cell; @@ -699,16 +699,16 @@ class html_table extends html } $cell = new stdClass; - $cell->attrib = $attr; - $cell->content = $cont; + $cell->attrib = $attr; + $cell->content = $cont; $this->header[] = $cell; } - /** + /** * Remove a column from a table * Useful for plugins making alterations - * - * @param string $class + * + * @param string $class */ public function remove_column($class) { @@ -788,8 +788,9 @@ class html_table extends html */ public function show($attrib = null) { - if (is_array($attrib)) + if (is_array($attrib)) { $this->attrib = array_merge($this->attrib, $attrib); + } $thead = $tbody = ""; @@ -831,7 +832,7 @@ class html_table extends html */ public function size() { - return count($this->rows); + return count($this->rows); } /** diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php index a8f274a8f..98d8f98ee 100644 --- a/program/lib/Roundcube/rcube_addressbook.php +++ b/program/lib/Roundcube/rcube_addressbook.php @@ -138,7 +138,7 @@ abstract class rcube_addressbook */ function get_error() { - return $this->error; + return $this->error; } /** @@ -149,7 +149,7 @@ abstract class rcube_addressbook */ protected function set_error($type, $message) { - $this->error = array('type' => $type, 'message' => $message); + $this->error = array('type' => $type, 'message' => $message); } /** diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php index 69eaabedc..1aa5d5856 100644 --- a/program/lib/Roundcube/rcube_session.php +++ b/program/lib/Roundcube/rcube_session.php @@ -28,602 +28,629 @@ */ class rcube_session { - private $db; - private $ip; - private $start; - private $changed; - private $unsets = array(); - private $gc_handlers = array(); - private $cookiename = 'roundcube_sessauth'; - private $vars; - private $key; - private $now; - private $secret = ''; - private $ip_check = false; - private $logging = false; - private $memcache; - - /** - * Default constructor - */ - public function __construct($db, $config) - { - $this->db = $db; - $this->start = microtime(true); - $this->ip = $_SERVER['REMOTE_ADDR']; - $this->logging = $config->get('log_session', false); - - $lifetime = $config->get('session_lifetime', 1) * 60; - $this->set_lifetime($lifetime); - - // use memcache backend - if ($config->get('session_storage', 'db') == 'memcache') { - $this->memcache = rcube::get_instance()->get_memcache(); - - // set custom functions for PHP session management if memcache is available - if ($this->memcache) { - session_set_save_handler( - array($this, 'open'), - array($this, 'close'), - array($this, 'mc_read'), - array($this, 'mc_write'), - array($this, 'mc_destroy'), - array($this, 'gc')); - } - else { - rcube::raise_error(array('code' => 604, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => "Failed to connect to memcached. Please check configuration"), - true, true); - } + private $db; + private $ip; + private $start; + private $changed; + private $unsets = array(); + private $gc_handlers = array(); + private $cookiename = 'roundcube_sessauth'; + private $vars; + private $key; + private $now; + private $secret = ''; + private $ip_check = false; + private $logging = false; + private $memcache; + + + /** + * Default constructor + */ + public function __construct($db, $config) + { + $this->db = $db; + $this->start = microtime(true); + $this->ip = $_SERVER['REMOTE_ADDR']; + $this->logging = $config->get('log_session', false); + + $lifetime = $config->get('session_lifetime', 1) * 60; + $this->set_lifetime($lifetime); + + // use memcache backend + if ($config->get('session_storage', 'db') == 'memcache') { + $this->memcache = rcube::get_instance()->get_memcache(); + + // set custom functions for PHP session management if memcache is available + if ($this->memcache) { + session_set_save_handler( + array($this, 'open'), + array($this, 'close'), + array($this, 'mc_read'), + array($this, 'mc_write'), + array($this, 'mc_destroy'), + array($this, 'gc')); + } + else { + rcube::raise_error(array('code' => 604, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Failed to connect to memcached. Please check configuration"), + true, true); + } + } + else { + // set custom functions for PHP session management + session_set_save_handler( + array($this, 'open'), + array($this, 'close'), + array($this, 'db_read'), + array($this, 'db_write'), + array($this, 'db_destroy'), + array($this, 'db_gc')); + } } - else { - // set custom functions for PHP session management - session_set_save_handler( - array($this, 'open'), - array($this, 'close'), - array($this, 'db_read'), - array($this, 'db_write'), - array($this, 'db_destroy'), - array($this, 'db_gc')); - } - } - - - public function open($save_path, $session_name) - { - return true; - } - - - public function close() - { - return true; - } - - - /** - * Delete session data for the given key - * - * @param string Session ID - */ - public function destroy($key) - { - return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key); - } - - - /** - * Read session data from database - * - * @param string Session ID - * @return string Session vars - */ - public function db_read($key) - { - $sql_result = $this->db->query( - "SELECT vars, ip, changed FROM ".$this->db->table_name('session') - ." WHERE sess_id = ?", $key); - - if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { - $this->changed = strtotime($sql_arr['changed']); - $this->ip = $sql_arr['ip']; - $this->vars = base64_decode($sql_arr['vars']); - $this->key = $key; - - return !empty($this->vars) ? (string) $this->vars : ''; + + + public function open($save_path, $session_name) + { + return true; + } + + + public function close() + { + return true; } - return null; - } - - - /** - * Save session data. - * handler for session_read() - * - * @param string Session ID - * @param string Serialized session vars - * @return boolean True on success - */ - public function db_write($key, $vars) - { - $ts = microtime(true); - $now = $this->db->fromunixtime((int)$ts); - - // no session row in DB (db_read() returns false) - if (!$this->key) { - $oldvars = null; + + /** + * Delete session data for the given key + * + * @param string Session ID + */ + public function destroy($key) + { + return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key); + } + + + /** + * Read session data from database + * + * @param string Session ID + * + * @return string Session vars + */ + public function db_read($key) + { + $sql_result = $this->db->query( + "SELECT vars, ip, changed FROM ".$this->db->table_name('session') + ." WHERE sess_id = ?", $key); + + if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { + $this->changed = strtotime($sql_arr['changed']); + $this->ip = $sql_arr['ip']; + $this->vars = base64_decode($sql_arr['vars']); + $this->key = $key; + + return !empty($this->vars) ? (string) $this->vars : ''; + } + + return null; } - // use internal data from read() for fast requests (up to 0.5 sec.) - else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) { - $oldvars = $this->vars; + + + /** + * Save session data. + * handler for session_read() + * + * @param string Session ID + * @param string Serialized session vars + * + * @return boolean True on success + */ + public function db_write($key, $vars) + { + $ts = microtime(true); + $now = $this->db->fromunixtime((int)$ts); + + // no session row in DB (db_read() returns false) + if (!$this->key) { + $oldvars = null; + } + // use internal data from read() for fast requests (up to 0.5 sec.) + else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) { + $oldvars = $this->vars; + } + else { // else read data again from DB + $oldvars = $this->db_read($key); + } + + if ($oldvars !== null) { + $newvars = $this->_fixvars($vars, $oldvars); + + if ($newvars !== $oldvars) { + $this->db->query( + sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?", + $this->db->table_name('session'), $now), + base64_encode($newvars), $key); + } + else if ($ts - $this->changed > $this->lifetime / 2) { + $this->db->query("UPDATE ".$this->db->table_name('session') + ." SET changed=$now WHERE sess_id=?", $key); + } + } + else { + $this->db->query( + sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ". + "VALUES (?, ?, ?, %s, %s)", + $this->db->table_name('session'), $now, $now), + $key, base64_encode($vars), (string)$this->ip); + } + + return true; + } + + + /** + * Merge vars with old vars and apply unsets + */ + private function _fixvars($vars, $oldvars) + { + if ($oldvars !== null) { + $a_oldvars = $this->unserialize($oldvars); + if (is_array($a_oldvars)) { + foreach ((array)$this->unsets as $k) + unset($a_oldvars[$k]); + + $newvars = $this->serialize(array_merge( + (array)$a_oldvars, (array)$this->unserialize($vars))); + } + else { + $newvars = $vars; + } + } + + $this->unsets = array(); + return $newvars; } - else { // else read data again from DB - $oldvars = $this->db_read($key); + + + /** + * Handler for session_destroy() + * + * @param string Session ID + * + * @return boolean True on success + */ + public function db_destroy($key) + { + if ($key) { + $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?", + $this->db->table_name('session')), $key); + } + + return true; } - if ($oldvars !== null) { - $newvars = $this->_fixvars($vars, $oldvars); - if ($newvars !== $oldvars) { + /** + * Garbage collecting function + * + * @param string Session lifetime in seconds + * @return boolean True on success + */ + public function db_gc($maxlifetime) + { + // just delete all expired sessions $this->db->query( - sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?", - $this->db->table_name('session'), $now), - base64_encode($newvars), $key); - } - else if ($ts - $this->changed > $this->lifetime / 2) { - $this->db->query("UPDATE ".$this->db->table_name('session')." SET changed=$now WHERE sess_id=?", $key); - } + sprintf("DELETE FROM %s WHERE changed < %s", + $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime))); + + $this->gc(); + + return true; + } + + + /** + * Read session data from memcache + * + * @param string Session ID + * @return string Session vars + */ + public function mc_read($key) + { + if ($value = $this->memcache->get($key)) { + $arr = unserialize($value); + $this->changed = $arr['changed']; + $this->ip = $arr['ip']; + $this->vars = $arr['vars']; + $this->key = $key; + + return !empty($this->vars) ? (string) $this->vars : ''; + } + + return null; + } + + + /** + * Save session data. + * handler for session_read() + * + * @param string Session ID + * @param string Serialized session vars + * + * @return boolean True on success + */ + public function mc_write($key, $vars) + { + $ts = microtime(true); + + // no session data in cache (mc_read() returns false) + if (!$this->key) + $oldvars = null; + // use internal data for fast requests (up to 0.5 sec.) + else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) + $oldvars = $this->vars; + else // else read data again + $oldvars = $this->mc_read($key); + + $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars; + + if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2) { + return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), + MEMCACHE_COMPRESSED, $this->lifetime); + } + + return true; + } + + + /** + * Handler for session_destroy() with memcache backend + * + * @param string Session ID + * + * @return boolean True on success + */ + public function mc_destroy($key) + { + if ($key) { + // #1488592: use 2nd argument + $this->memcache->delete($key, 0); + } + + return true; + } + + + /** + * Execute registered garbage collector routines + */ + public function gc() + { + foreach ($this->gc_handlers as $fct) { + call_user_func($fct); + } } - else { - $this->db->query( - sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ". - "VALUES (?, ?, ?, %s, %s)", - $this->db->table_name('session'), $now, $now), - $key, base64_encode($vars), (string)$this->ip); + + + /** + * Register additional garbage collector functions + * + * @param mixed Callback function + */ + public function register_gc_handler($func) + { + foreach ($this->gc_handlers as $handler) { + if ($handler == $func) { + return; + } + } + + $this->gc_handlers[] = $func; } - return true; - } - - - /** - * Merge vars with old vars and apply unsets - */ - private function _fixvars($vars, $oldvars) - { - if ($oldvars !== null) { - $a_oldvars = $this->unserialize($oldvars); - if (is_array($a_oldvars)) { - foreach ((array)$this->unsets as $k) - unset($a_oldvars[$k]); - - $newvars = $this->serialize(array_merge( - (array)$a_oldvars, (array)$this->unserialize($vars))); - } - else - $newvars = $vars; + + /** + * Generate and set new session id + * + * @param boolean $destroy If enabled the current session will be destroyed + */ + public function regenerate_id($destroy=true) + { + session_regenerate_id($destroy); + + $this->vars = null; + $this->key = session_id(); + + return true; } - $this->unsets = array(); - return $newvars; - } - - - /** - * Handler for session_destroy() - * - * @param string Session ID - * - * @return boolean True on success - */ - public function db_destroy($key) - { - if ($key) { - $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?", $this->db->table_name('session')), $key); + + /** + * Unset a session variable + * + * @param string Varibale name + * @return boolean True on success + */ + public function remove($var=null) + { + if (empty($var)) { + return $this->destroy(session_id()); + } + + $this->unsets[] = $var; + unset($_SESSION[$var]); + + return true; + } + + + /** + * Kill this session + */ + public function kill() + { + $this->vars = null; + $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed) + $this->destroy(session_id()); + rcube_utils::setcookie($this->cookiename, '-del-', time() - 60); + } + + + /** + * Re-read session data from storage backend + */ + public function reload() + { + if ($this->key && $this->memcache) + $data = $this->mc_read($this->key); + else if ($this->key) + $data = $this->db_read($this->key); + + if ($data) + session_decode($data); + } + + + /** + * Serialize session data + */ + private function serialize($vars) + { + $data = ''; + if (is_array($vars)) { + foreach ($vars as $var=>$value) + $data .= $var.'|'.serialize($value); + } + else { + $data = 'b:0;'; + } + + return $data; } - return true; - } - - - /** - * Garbage collecting function - * - * @param string Session lifetime in seconds - * @return boolean True on success - */ - public function db_gc($maxlifetime) - { - // just delete all expired sessions - $this->db->query( - sprintf("DELETE FROM %s WHERE changed < %s", - $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime))); - - $this->gc(); - - return true; - } - - - /** - * Read session data from memcache - * - * @param string Session ID - * @return string Session vars - */ - public function mc_read($key) - { - if ($value = $this->memcache->get($key)) { - $arr = unserialize($value); - $this->changed = $arr['changed']; - $this->ip = $arr['ip']; - $this->vars = $arr['vars']; - $this->key = $key; - - return !empty($this->vars) ? (string) $this->vars : ''; + + /** + * Unserialize session data + * http://www.php.net/manual/en/function.session-decode.php#56106 + */ + private function unserialize($str) + { + $str = (string)$str; + $endptr = strlen($str); + $p = 0; + + $serialized = ''; + $items = 0; + $level = 0; + + while ($p < $endptr) { + $q = $p; + while ($str[$q] != '|') + if (++$q >= $endptr) + break 2; + + if ($str[$p] == '!') { + $p++; + $has_value = false; + } + else { + $has_value = true; + } + + $name = substr($str, $p, $q - $p); + $q++; + + $serialized .= 's:' . strlen($name) . ':"' . $name . '";'; + + if ($has_value) { + for (;;) { + $p = $q; + switch (strtolower($str[$q])) { + case 'n': // null + case 'b': // boolean + case 'i': // integer + case 'd': // decimal + do $q++; + while ( ($q < $endptr) && ($str[$q] != ';') ); + $q++; + $serialized .= substr($str, $p, $q - $p); + if ($level == 0) + break 2; + break; + case 'r': // reference + $q+= 2; + for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) + $id .= $str[$q]; + $q++; + // increment pointer because of outer array + $serialized .= 'R:' . ($id + 1) . ';'; + if ($level == 0) + break 2; + break; + case 's': // string + $q+=2; + for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) + $length .= $str[$q]; + $q+=2; + $q+= (int)$length + 2; + $serialized .= substr($str, $p, $q - $p); + if ($level == 0) + break 2; + break; + case 'a': // array + case 'o': // object + do $q++; + while ($q < $endptr && $str[$q] != '{'); + $q++; + $level++; + $serialized .= substr($str, $p, $q - $p); + break; + case '}': // end of array|object + $q++; + $serialized .= substr($str, $p, $q - $p); + if (--$level == 0) + break 2; + break; + default: + return false; + } + } + } + else { + $serialized .= 'N;'; + $q += 2; + } + $items++; + $p = $q; + } + + return unserialize( 'a:' . $items . ':{' . $serialized . '}' ); } - return null; - } - - - /** - * Save session data. - * handler for session_read() - * - * @param string Session ID - * @param string Serialized session vars - * @return boolean True on success - */ - public function mc_write($key, $vars) - { - $ts = microtime(true); - - // no session data in cache (mc_read() returns false) - if (!$this->key) - $oldvars = null; - // use internal data for fast requests (up to 0.5 sec.) - else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) - $oldvars = $this->vars; - else // else read data again - $oldvars = $this->mc_read($key); - - $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars; - - if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2) - return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), MEMCACHE_COMPRESSED, $this->lifetime); - - return true; - } - - - /** - * Handler for session_destroy() with memcache backend - * - * @param string Session ID - * - * @return boolean True on success - */ - public function mc_destroy($key) - { - if ($key) { - // #1488592: use 2nd argument - $this->memcache->delete($key, 0); + + /** + * Setter for session lifetime + */ + public function set_lifetime($lifetime) + { + $this->lifetime = max(120, $lifetime); + + // valid time range is now - 1/2 lifetime to now + 1/2 lifetime + $now = time(); + $this->now = $now - ($now % ($this->lifetime / 2)); } - return true; - } + /** + * Getter for remote IP saved with this session + */ + public function get_ip() + { + return $this->ip; + } - /** - * Execute registered garbage collector routines - */ - public function gc() - { - foreach ($this->gc_handlers as $fct) { - call_user_func($fct); + + /** + * Setter for cookie encryption secret + */ + function set_secret($secret) + { + $this->secret = $secret; } - } - - - /** - * Register additional garbage collector functions - * - * @param mixed Callback function - */ - public function register_gc_handler($func) - { - foreach ($this->gc_handlers as $handler) { - if ($handler == $func) { - return; - } + + + /** + * Enable/disable IP check + */ + function set_ip_check($check) + { + $this->ip_check = $check; } - $this->gc_handlers[] = $func; - } - - - /** - * Generate and set new session id - * - * @param boolean $destroy If enabled the current session will be destroyed - */ - public function regenerate_id($destroy=true) - { - session_regenerate_id($destroy); - - $this->vars = null; - $this->key = session_id(); - - return true; - } - - - /** - * Unset a session variable - * - * @param string Varibale name - * @return boolean True on success - */ - public function remove($var=null) - { - if (empty($var)) - return $this->destroy(session_id()); - - $this->unsets[] = $var; - unset($_SESSION[$var]); - - return true; - } - - - /** - * Kill this session - */ - public function kill() - { - $this->vars = null; - $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed) - $this->destroy(session_id()); - rcube_utils::setcookie($this->cookiename, '-del-', time() - 60); - } - - - /** - * Re-read session data from storage backend - */ - public function reload() - { - if ($this->key && $this->memcache) - $data = $this->mc_read($this->key); - else if ($this->key) - $data = $this->db_read($this->key); - - if ($data) - session_decode($data); - } - - - /** - * Serialize session data - */ - private function serialize($vars) - { - $data = ''; - if (is_array($vars)) - foreach ($vars as $var=>$value) - $data .= $var.'|'.serialize($value); - else - $data = 'b:0;'; - return $data; - } - - - /** - * Unserialize session data - * http://www.php.net/manual/en/function.session-decode.php#56106 - */ - private function unserialize($str) - { - $str = (string)$str; - $endptr = strlen($str); - $p = 0; - - $serialized = ''; - $items = 0; - $level = 0; - - while ($p < $endptr) { - $q = $p; - while ($str[$q] != '|') - if (++$q >= $endptr) break 2; - - if ($str[$p] == '!') { - $p++; - $has_value = false; - } else { - $has_value = true; - } - - $name = substr($str, $p, $q - $p); - $q++; - - $serialized .= 's:' . strlen($name) . ':"' . $name . '";'; - - if ($has_value) { - for (;;) { - $p = $q; - switch (strtolower($str[$q])) { - case 'n': /* null */ - case 'b': /* boolean */ - case 'i': /* integer */ - case 'd': /* decimal */ - do $q++; - while ( ($q < $endptr) && ($str[$q] != ';') ); - $q++; - $serialized .= substr($str, $p, $q - $p); - if ($level == 0) break 2; - break; - case 'r': /* reference */ - $q+= 2; - for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q]; - $q++; - $serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */ - if ($level == 0) break 2; - break; - case 's': /* string */ - $q+=2; - for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q]; - $q+=2; - $q+= (int)$length + 2; - $serialized .= substr($str, $p, $q - $p); - if ($level == 0) break 2; - break; - case 'a': /* array */ - case 'o': /* object */ - do $q++; - while ( ($q < $endptr) && ($str[$q] != '{') ); - $q++; - $level++; - $serialized .= substr($str, $p, $q - $p); - break; - case '}': /* end of array|object */ - $q++; - $serialized .= substr($str, $p, $q - $p); - if (--$level == 0) break 2; - break; - default: - return false; - } + + /** + * Setter for the cookie name used for session cookie + */ + function set_cookiename($cookiename) + { + if ($cookiename) { + $this->cookiename = $cookiename; } - } else { - $serialized .= 'N;'; - $q += 2; - } - $items++; - $p = $q; } - return unserialize( 'a:' . $items . ':{' . $serialized . '}' ); - } - - - /** - * Setter for session lifetime - */ - public function set_lifetime($lifetime) - { - $this->lifetime = max(120, $lifetime); - - // valid time range is now - 1/2 lifetime to now + 1/2 lifetime - $now = time(); - $this->now = $now - ($now % ($this->lifetime / 2)); - } - - - /** - * Getter for remote IP saved with this session - */ - public function get_ip() - { - return $this->ip; - } - - - /** - * Setter for cookie encryption secret - */ - function set_secret($secret) - { - $this->secret = $secret; - } - - - /** - * Enable/disable IP check - */ - function set_ip_check($check) - { - $this->ip_check = $check; - } - - - /** - * Setter for the cookie name used for session cookie - */ - function set_cookiename($cookiename) - { - if ($cookiename) - $this->cookiename = $cookiename; - } - - - /** - * Check session authentication cookie - * - * @return boolean True if valid, False if not - */ - function check_auth() - { - $this->cookie = $_COOKIE[$this->cookiename]; - $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true; - - if (!$result) - $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']); - - if ($result && $this->_mkcookie($this->now) != $this->cookie) { - $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now)); - $result = false; - - // Check if using id from a previous time slot - for ($i = 1; $i <= 2; $i++) { - $prev = $this->now - ($this->lifetime / 2) * $i; - if ($this->_mkcookie($prev) == $this->cookie) { - $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie); - $this->set_auth_cookie(); - $result = true; + + /** + * Check session authentication cookie + * + * @return boolean True if valid, False if not + */ + function check_auth() + { + $this->cookie = $_COOKIE[$this->cookiename]; + $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true; + + if (!$result) { + $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']); } - } + + if ($result && $this->_mkcookie($this->now) != $this->cookie) { + $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now)); + $result = false; + + // Check if using id from a previous time slot + for ($i = 1; $i <= 2; $i++) { + $prev = $this->now - ($this->lifetime / 2) * $i; + if ($this->_mkcookie($prev) == $this->cookie) { + $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie); + $this->set_auth_cookie(); + $result = true; + } + } + } + + if (!$result) { + $this->log("Session authentication failed for " . $this->key + . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev)); + } + + return $result; + } + + + /** + * Set session authentication cookie + */ + function set_auth_cookie() + { + $this->cookie = $this->_mkcookie($this->now); + rcube_utils::setcookie($this->cookiename, $this->cookie, 0); + $_COOKIE[$this->cookiename] = $this->cookie; } - if (!$result) - $this->log("Session authentication failed for " . $this->key . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev)); - - return $result; - } - - - /** - * Set session authentication cookie - */ - function set_auth_cookie() - { - $this->cookie = $this->_mkcookie($this->now); - rcube_utils::setcookie($this->cookiename, $this->cookie, 0); - $_COOKIE[$this->cookiename] = $this->cookie; - } - - - /** - * Create session cookie from session data - * - * @param int Time slot to use - */ - function _mkcookie($timeslot) - { - $auth_string = "$this->key,$this->secret,$timeslot"; - return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string)); - } - - /** - * Writes debug information to the log - */ - function log($line) - { - if ($this->logging) - rcube::write_log('session', $line); - } + /** + * Create session cookie from session data + * + * @param int Time slot to use + */ + function _mkcookie($timeslot) + { + $auth_string = "$this->key,$this->secret,$timeslot"; + return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string)); + } + + /** + * Writes debug information to the log + */ + function log($line) + { + if ($this->logging) { + rcube::write_log('session', $line); + } + } } diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php index 68288f54c..0fe982b26 100644 --- a/program/lib/Roundcube/rcube_string_replacer.php +++ b/program/lib/Roundcube/rcube_string_replacer.php @@ -24,164 +24,164 @@ */ class rcube_string_replacer { - public static $pattern = '/##str_replacement\[([0-9]+)\]##/'; - public $mailto_pattern; - public $link_pattern; - private $values = array(); - - - function __construct() - { - // Simplified domain expression for UTF8 characters handling - // Support unicode/punycode in top-level domain part - $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})'; - $url1 = '.:;,'; - $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-'; - - $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/"; - $this->mailto_pattern = "/(" - ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part - ."@$utf_domain" // domain-part - ."(\?[$url1$url2]+)?" // e.g. ?subject=test... - .")/"; - } - - /** - * Add a string to the internal list - * - * @param string String value - * @return int Index of value for retrieval - */ - public function add($str) - { - $i = count($this->values); - $this->values[$i] = $str; - return $i; - } - - /** - * Build replacement string - */ - public function get_replacement($i) - { - return '##str_replacement['.$i.']##'; - } - - /** - * Callback function used to build HTML links around URL strings - * - * @param array Matches result from preg_replace_callback - * @return int Index of saved string value - */ - public function link_callback($matches) - { - $i = -1; - $scheme = strtolower($matches[1]); - - if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) { - $url = $matches[1] . $matches[2]; + public static $pattern = '/##str_replacement\[([0-9]+)\]##/'; + public $mailto_pattern; + public $link_pattern; + private $values = array(); + + + function __construct() + { + // Simplified domain expression for UTF8 characters handling + // Support unicode/punycode in top-level domain part + $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})'; + $url1 = '.:;,'; + $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-'; + + $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/"; + $this->mailto_pattern = "/(" + ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part + ."@$utf_domain" // domain-part + ."(\?[$url1$url2]+)?" // e.g. ?subject=test... + .")/"; } - else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) { - $url = $m[2] . $matches[2]; - $url_prefix = 'http://'; - $prefix = $m[1]; + + /** + * Add a string to the internal list + * + * @param string String value + * @return int Index of value for retrieval + */ + public function add($str) + { + $i = count($this->values); + $this->values[$i] = $str; + return $i; } - if ($url) { - $suffix = $this->parse_url_brackets($url); - $i = $this->add($prefix . html::a(array( - 'href' => $url_prefix . $url, - 'target' => '_blank' - ), rcube::Q($url)) . $suffix); + /** + * Build replacement string + */ + public function get_replacement($i) + { + return '##str_replacement['.$i.']##'; } - // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes. - return $i >= 0 ? $this->get_replacement($i) : $matches[0]; - } - - /** - * Callback function used to build mailto: links around e-mail strings - * - * @param array Matches result from preg_replace_callback - * @return int Index of saved string value - */ - public function mailto_callback($matches) - { - $href = $matches[1]; - $suffix = $this->parse_url_brackets($href); - $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix); - - return $i >= 0 ? $this->get_replacement($i) : ''; - } - - /** - * Look up the index from the preg_replace matches array - * and return the substitution value. - * - * @param array Matches result from preg_replace_callback - * @return string Value at index $matches[1] - */ - public function replace_callback($matches) - { - return $this->values[$matches[1]]; - } - - /** - * Replace all defined (link|mailto) patterns with replacement string - * - * @param string $str Text - * - * @return string Text - */ - public function replace($str) - { - // search for patterns like links and e-mail addresses - $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str); - $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str); - - return $str; - } - - /** - * Replace substituted strings with original values - */ - public function resolve($str) - { - return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str); - } - - /** - * Fixes bracket characters in URL handling - */ - public static function parse_url_brackets(&$url) - { - // #1487672: special handling of square brackets, - // URL regexp allows [] characters in URL, for example: - // "http://example.com/?a[b]=c". However we need to handle - // properly situation when a bracket is placed at the end - // of the link e.g. "[http://example.com]" - if (preg_match('/(\\[|\\])/', $url)) { - $in = false; - for ($i=0, $len=strlen($url); $i<$len; $i++) { - if ($url[$i] == '[') { - if ($in) - break; - $in = true; + /** + * Callback function used to build HTML links around URL strings + * + * @param array Matches result from preg_replace_callback + * @return int Index of saved string value + */ + public function link_callback($matches) + { + $i = -1; + $scheme = strtolower($matches[1]); + + if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) { + $url = $matches[1] . $matches[2]; + } + else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) { + $url = $m[2] . $matches[2]; + $url_prefix = 'http://'; + $prefix = $m[1]; } - else if ($url[$i] == ']') { - if (!$in) - break; - $in = false; + + if ($url) { + $suffix = $this->parse_url_brackets($url); + $i = $this->add($prefix . html::a(array( + 'href' => $url_prefix . $url, + 'target' => '_blank' + ), rcube::Q($url)) . $suffix); } - } - if ($i<$len) { - $suffix = substr($url, $i); - $url = substr($url, 0, $i); - } + // Return valid link for recognized schemes, otherwise + // return the unmodified string for unrecognized schemes. + return $i >= 0 ? $this->get_replacement($i) : $matches[0]; } - return $suffix; - } + /** + * Callback function used to build mailto: links around e-mail strings + * + * @param array Matches result from preg_replace_callback + * @return int Index of saved string value + */ + public function mailto_callback($matches) + { + $href = $matches[1]; + $suffix = $this->parse_url_brackets($href); + $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix); + + return $i >= 0 ? $this->get_replacement($i) : ''; + } + /** + * Look up the index from the preg_replace matches array + * and return the substitution value. + * + * @param array Matches result from preg_replace_callback + * @return string Value at index $matches[1] + */ + public function replace_callback($matches) + { + return $this->values[$matches[1]]; + } + + /** + * Replace all defined (link|mailto) patterns with replacement string + * + * @param string $str Text + * + * @return string Text + */ + public function replace($str) + { + // search for patterns like links and e-mail addresses + $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str); + $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str); + + return $str; + } + + /** + * Replace substituted strings with original values + */ + public function resolve($str) + { + return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str); + } + + /** + * Fixes bracket characters in URL handling + */ + public static function parse_url_brackets(&$url) + { + // #1487672: special handling of square brackets, + // URL regexp allows [] characters in URL, for example: + // "http://example.com/?a[b]=c". However we need to handle + // properly situation when a bracket is placed at the end + // of the link e.g. "[http://example.com]" + if (preg_match('/(\\[|\\])/', $url)) { + $in = false; + for ($i=0, $len=strlen($url); $i<$len; $i++) { + if ($url[$i] == '[') { + if ($in) + break; + $in = true; + } + else if ($url[$i] == ']') { + if (!$in) + break; + $in = false; + } + } + + if ($i < $len) { + $suffix = substr($url, $i); + $url = substr($url, 0, $i); + } + } + + return $suffix; + } } -- cgit v1.2.3 From c5d7c941aa16b5bd98cfd56f12bbe7c39bddc608 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 19 Dec 2012 11:43:52 +0100 Subject: Add unsupported alternative parts to attachments list (#1488870) --- CHANGELOG | 1 + program/lib/Roundcube/rcube_message.php | 63 ++++++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 17 deletions(-) (limited to 'program/lib') diff --git a/CHANGELOG b/CHANGELOG index 41e266d0c..c4e63aaeb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Add unsupported alternative parts to attachments list (#1488870) - Add Compose button on message view page (#1488747) - Display 'Sender' header in message preview - Plugin API: Added message_before_send hook diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index f41493d12..08b94d8d9 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -106,6 +106,7 @@ class rcube_message if (!empty($this->headers->structure)) { $this->get_mime_numbers($this->headers->structure); $this->parse_structure($this->headers->structure); + $this->parse_attachments(); } else { $this->body = $this->storage->get_body($uid); @@ -333,7 +334,7 @@ class rcube_message if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) { $c = new stdClass; $c->type = 'headers'; - $c->headers = &$structure->headers; + $c->headers = $structure->headers; $this->parts[] = $c; } @@ -351,7 +352,7 @@ class rcube_message // print body if message doesn't have multiple parts if ($message_ctype_primary == 'text' && !$recursive) { $structure->type = 'content'; - $this->parts[] = &$structure; + $this->parts[] = $structure; // Parse simple (plain text) message body if ($message_ctype_secondary == 'plain') @@ -363,32 +364,39 @@ class rcube_message // the same for pgp signed messages else if ($mimetype == 'application/pgp' && !$recursive) { $structure->type = 'content'; - $this->parts[] = &$structure; + $this->parts[] = $structure; } // message contains (more than one!) alternative parts else if ($mimetype == 'multipart/alternative' && is_array($structure->parts) && count($structure->parts) > 1 ) { - // get html/plaintext parts - $plain_part = $html_part = $print_part = $related_part = null; + $plain_part = null; + $html_part = null; + $print_part = null; + $related_part = null; + $attach_part = null; + // get html/plaintext parts, other add to attachments list foreach ($structure->parts as $p => $sub_part) { $sub_mimetype = $sub_part->mimetype; + $is_multipart = in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative')); // skip empty text parts - if (!$sub_part->size && preg_match('#^text/(plain|html|enriched)$#', $sub_mimetype)) { + if (!$sub_part->size && !$is_multipart) { continue; } // check if sub part is - if ($sub_mimetype == 'text/plain') + if ($is_multipart) + $related_part = $p; + else if ($sub_mimetype == 'text/plain') $plain_part = $p; else if ($sub_mimetype == 'text/html') $html_part = $p; else if ($sub_mimetype == 'text/enriched') $enriched_part = $p; - else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative'))) - $related_part = $p; + else + $attach_part = $p; } // parse related part (alternative part could be in here) @@ -404,13 +412,13 @@ class rcube_message // choose html/plain part to print if ($html_part !== null && $this->opt['prefer_html']) { - $print_part = &$structure->parts[$html_part]; + $print_part = $structure->parts[$html_part]; } else if ($enriched_part !== null) { - $print_part = &$structure->parts[$enriched_part]; + $print_part = $structure->parts[$enriched_part]; } else if ($plain_part !== null) { - $print_part = &$structure->parts[$plain_part]; + $print_part = $structure->parts[$plain_part]; } // add the right message body @@ -432,11 +440,16 @@ class rcube_message // add html part as attachment if ($html_part !== null && $structure->parts[$html_part] !== $print_part) { - $html_part = &$structure->parts[$html_part]; + $html_part = $structure->parts[$html_part]; $html_part->mimetype = 'text/html'; $this->attachments[] = $html_part; } + + // add unsupported/unrecognized parts to attachments list + if ($attach_part) { + $this->attachments[] = $structure->parts[$attach_part]; + } } // this is an ecrypted message -> create a plaintext body with the according message else if ($mimetype == 'multipart/encrypted') { @@ -561,9 +574,6 @@ class rcube_message // regular attachment with valid content type // (content-type name regexp according to RFC4288.4.2) else if (preg_match('/^[a-z0-9!#$&.+^_-]+\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) { - if (!$mail_part->filename) - $mail_part->filename = 'Part '.$mail_part->mime_id; - $this->attachments[] = $mail_part; } // attachment with invalid content type @@ -628,12 +638,31 @@ class rcube_message } // message is a single part non-text (without filename) else if (preg_match('/application\//i', $mimetype)) { - $structure->filename = 'Part '.$structure->mime_id; $this->attachments[] = $structure; } } + /** + * Parse attachment parts + */ + private function parse_attachments() + { + // Attachment must have a name + foreach ($this->attachments as $attachment) { + if (!$attachment->filename) { + $ext = rcube_mime::get_mime_extensions($attachment->mimetype); + $ext = array_shift($ext); + + $attachment->filename = 'Part_' . $attachment->mime_id; + if ($ext) { + $attachment->filename .= '.' . $ext; + } + } + } + } + + /** * Fill aflat array with references to all parts, indexed by part numbers * -- cgit v1.2.3 From 9ac96015f274b9451377d05001097d0bb7526a27 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 19 Dec 2012 12:27:52 +0100 Subject: Better GD module functions detection, should fix "Call to undefined function imagecreatefromjpeg()" error --- program/lib/Roundcube/rcube_image.php | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php index ad96842d2..9695022da 100644 --- a/program/lib/Roundcube/rcube_image.php +++ b/program/lib/Roundcube/rcube_image.php @@ -128,17 +128,20 @@ class rcube_image } // use GD extension - $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG); - if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) { - if ($props['gd_type'] == IMAGETYPE_JPEG) { + if ($props['gd_type']) { + if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) { $image = imagecreatefromjpeg($this->image_file); } - elseif($props['gd_type'] == IMAGETYPE_GIF) { + else if($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) { $image = imagecreatefromgif($this->image_file); } - elseif($props['gd_type'] == IMAGETYPE_PNG) { + else if($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) { $image = imagecreatefrompng($this->image_file); } + else { + // @TODO: print error to the log? + return false; + } $scale = $size / max($props['width'], $props['height']); $width = $props['width'] * $scale; @@ -216,19 +219,22 @@ class rcube_image } // use GD extension (TIFF isn't supported) - $props = $this->props(); - $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG); + $props = $this->props(); - if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) { - if ($props['gd_type'] == IMAGETYPE_JPEG) { + if ($props['gd_type']) { + if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) { $image = imagecreatefromjpeg($this->image_file); } - else if ($props['gd_type'] == IMAGETYPE_GIF) { + else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) { $image = imagecreatefromgif($this->image_file); } - else if ($props['gd_type'] == IMAGETYPE_PNG) { + else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) { $image = imagecreatefrompng($this->image_file); } + else { + // @TODO: print error to the log? + return false; + } if ($type == self::TYPE_JPG) { $result = imagejpeg($image, $filename, 75); -- cgit v1.2.3 From c23dc87f2b76ac76968c73bc9f31920b316282c6 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 19 Dec 2012 13:01:45 +0100 Subject: Don't display message parts with unsupported text type, e.g. text/calendar --- program/lib/Roundcube/rcube_message.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index 08b94d8d9..c45dbfcd0 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -351,15 +351,22 @@ class rcube_message // print body if message doesn't have multiple parts if ($message_ctype_primary == 'text' && !$recursive) { + // parts with unsupported type add to attachments list + if (!in_array($message_ctype_secondary, array('plain', 'html', 'enriched'))) { + $this->attachments[] = $structure; + return; + } + $structure->type = 'content'; $this->parts[] = $structure; // Parse simple (plain text) message body - if ($message_ctype_secondary == 'plain') + if ($message_ctype_secondary == 'plain') { foreach ((array)$this->uu_decode($structure) as $uupart) { $this->mime_parts[$uupart->mime_id] = $uupart; $this->attachments[] = $uupart; } + } } // the same for pgp signed messages else if ($mimetype == 'application/pgp' && !$recursive) { -- cgit v1.2.3 From a8ffab3f4ff54af41c9041a30eb989954d6ffc17 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 19 Dec 2012 14:15:11 +0100 Subject: Fix Call to undefined method rcube_db_sqlite::_get_result() --- program/lib/Roundcube/rcube_db_sqlite.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_db_sqlite.php b/program/lib/Roundcube/rcube_db_sqlite.php index 326c6a710..145b8a371 100644 --- a/program/lib/Roundcube/rcube_db_sqlite.php +++ b/program/lib/Roundcube/rcube_db_sqlite.php @@ -120,12 +120,7 @@ class rcube_db_sqlite extends rcube_db $q = $this->query('SELECT name FROM sqlite_master' .' WHERE type = \'table\' ORDER BY name'); - if ($res = $this->_get_result($q)) { - $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0); - } - else { - $this->tables = array(); - } + $this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array(); } return $this->tables; -- cgit v1.2.3 From a079269166d120bcbcb33d34f4b1c8f60d102e32 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 20 Dec 2012 08:53:48 +0100 Subject: Fix version comparisons with -stable suffix (#1488876) --- CHANGELOG | 1 + bin/installto.sh | 2 +- bin/update.sh | 4 ++-- installer/rcube_install.php | 6 +++--- program/lib/Roundcube/bootstrap.php | 16 ++++++++++++++++ tests/Framework/Bootstrap.php | 8 ++++++++ 6 files changed, 31 insertions(+), 6 deletions(-) (limited to 'program/lib') diff --git a/CHANGELOG b/CHANGELOG index c4e63aaeb..02e20455c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix version comparisons with -stable suffix (#1488876) - Add unsupported alternative parts to attachments list (#1488870) - Add Compose button on message view page (#1488747) - Display 'Sender' header in message preview diff --git a/bin/installto.sh b/bin/installto.sh index de96bf004..e6cf79d7d 100755 --- a/bin/installto.sh +++ b/bin/installto.sh @@ -35,7 +35,7 @@ if (!preg_match('/define\(.RCMAIL_VERSION.,\s*.([0-9.]+[a-z-]*)/', $iniset, $m)) $oldversion = $m[1]; -if (version_compare($oldversion, RCMAIL_VERSION, '>=')) +if (version_compare(version_parse($oldversion), version_parse(RCMAIL_VERSION), '>=')) die("Installation at target location is up-to-date!\n"); echo "Upgrading from $oldversion. Do you want to continue? (y/N)\n"; diff --git a/bin/update.sh b/bin/update.sh index 59aa596dd..2015aa904 100755 --- a/bin/update.sh +++ b/bin/update.sh @@ -34,7 +34,7 @@ if (!$opts['version']) { $opts['version'] = $input; } -if ($opts['version'] && version_compare($opts['version'], RCMAIL_VERSION, '>')) +if ($opts['version'] && version_compare(version_parse($opts['version']), version_parse(RCMAIL_VERSION), '>')) die("Nothing to be done here. Bye!\n"); @@ -169,7 +169,7 @@ if ($RCI->configured) { } // index contacts for fulltext searching - if (version_compare($opts['version'], '0.6', '<')) { + if (version_compare(version_parse($opts['version']), '0.6.0', '<')) { system(INSTALL_PATH . 'bin/indexcontacts.sh'); } diff --git a/installer/rcube_install.php b/installer/rcube_install.php index dfd63562d..530be3ebe 100644 --- a/installer/rcube_install.php +++ b/installer/rcube_install.php @@ -633,8 +633,8 @@ class rcube_install */ function update_db($DB, $version) { - $version = strtolower($version); - $engine = isset($this->db_map[$DB->db_provider]) ? $this->db_map[$DB->db_provider] : $DB->db_provider; + $version = version_parse(strtolower($version)); + $engine = isset($this->db_map[$DB->db_provider]) ? $this->db_map[$DB->db_provider] : $DB->db_provider; // read schema file from /SQL/* $fname = INSTALL_PATH . "SQL/$engine.update.sql"; @@ -643,7 +643,7 @@ class rcube_install foreach ($lines as $line) { $is_comment = preg_match('/^--/', $line); if (!$from && $is_comment && preg_match('/from version\s([0-9.]+[a-z-]*)/', $line, $m)) { - $v = strtolower($m[1]); + $v = version_parse(strtolower($m[1])); if ($v == $version || version_compare($version, $v, '<=')) $from = true; } diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php index 18c07ddab..530a7f855 100644 --- a/program/lib/Roundcube/bootstrap.php +++ b/program/lib/Roundcube/bootstrap.php @@ -357,6 +357,22 @@ function format_email($email) } +/** + * Fix version number so it can be used correctly in version_compare() + * + * @param string $version Version number string + * + * @param return Version number string + */ +function version_parse($version) +{ + return str_replace( + array('-stable', '-git'), + array('.0', '.99'), + $version); +} + + /** * mbstring replacement functions */ diff --git a/tests/Framework/Bootstrap.php b/tests/Framework/Bootstrap.php index d18fd371b..904be7e3b 100644 --- a/tests/Framework/Bootstrap.php +++ b/tests/Framework/Bootstrap.php @@ -207,4 +207,12 @@ class Framework_Bootstrap extends PHPUnit_Framework_TestCase $this->assertFalse($result, "Invalid ASCII (UTF-8 character [2])"); } + /** + * bootstrap.php: version_parse() + */ + function test_version_parse() + { + $this->assertEquals('0.9.0', version_parse('0.9-stable')); + $this->assertEquals('0.9.99', version_parse('0.9-git')); + } } -- cgit v1.2.3 From a61326c141816b5365613c206ef9ac350bfd9935 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 20 Dec 2012 19:51:37 +0100 Subject: Fix locking issue in SQLite driver (#1488874) --- program/lib/Roundcube/rcube_db.php | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 47ddc81a6..9283bb0d4 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -400,6 +400,10 @@ class rcube_db $this->debug($query); + // destroy reference to previous result, required for SQLite driver (#1488874) + $this->last_result = null; + + // send query $query = $this->dbh->query($query); if ($query === false) { -- cgit v1.2.3 From c4781306a54006727b05a5b8a5414e09c51b8bef Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 21 Dec 2012 09:13:11 +0100 Subject: CS fixes --- program/lib/Roundcube/rcube_plugin.php | 647 +++++++++++---------- program/lib/Roundcube/rcube_plugin_api.php | 891 +++++++++++++++-------------- 2 files changed, 782 insertions(+), 756 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php index 06247d456..66e77cce2 100644 --- a/program/lib/Roundcube/rcube_plugin.php +++ b/program/lib/Roundcube/rcube_plugin.php @@ -3,7 +3,7 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2009, The Roundcube Dev Team | + | Copyright (C) 2008-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -25,334 +25,353 @@ */ abstract class rcube_plugin { - /** - * Class name of the plugin instance - * - * @var string - */ - public $ID; - - /** - * Instance of Plugin API - * - * @var rcube_plugin_api - */ - public $api; - - /** - * Regular expression defining task(s) to bind with - * - * @var string - */ - public $task; - - /** - * Disables plugin in AJAX requests - * - * @var boolean - */ - public $noajax = false; - - /** - * Disables plugin in framed mode - * - * @var boolean - */ - public $noframe = false; - - protected $home; - protected $urlbase; - private $mytask; - - - /** - * Default constructor. - * - * @param rcube_plugin_api $api Plugin API - */ - public function __construct($api) - { - $this->ID = get_class($this); - $this->api = $api; - $this->home = $api->dir . $this->ID; - $this->urlbase = $api->url . $this->ID . '/'; - } - - /** - * Initialization method, needs to be implemented by the plugin itself - */ - abstract function init(); - - - /** - * Attempt to load the given plugin which is required for the current plugin - * - * @param string Plugin name - * @return boolean True on success, false on failure - */ - public function require_plugin($plugin_name) - { - return $this->api->load_plugin($plugin_name); - } - - - /** - * Load local config file from plugins directory. - * The loaded values are patched over the global configuration. - * - * @param string $fname Config file name relative to the plugin's folder - * @return boolean True on success, false on failure - */ - public function load_config($fname = 'config.inc.php') - { - $fpath = $this->home.'/'.$fname; - $rcube = rcube::get_instance(); - if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) { - rcube::raise_error(array( - 'code' => 527, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Failed to load config from $fpath"), true, false); - return false; + /** + * Class name of the plugin instance + * + * @var string + */ + public $ID; + + /** + * Instance of Plugin API + * + * @var rcube_plugin_api + */ + public $api; + + /** + * Regular expression defining task(s) to bind with + * + * @var string + */ + public $task; + + /** + * Disables plugin in AJAX requests + * + * @var boolean + */ + public $noajax = false; + + /** + * Disables plugin in framed mode + * + * @var boolean + */ + public $noframe = false; + + protected $home; + protected $urlbase; + private $mytask; + + + /** + * Default constructor. + * + * @param rcube_plugin_api $api Plugin API + */ + public function __construct($api) + { + $this->ID = get_class($this); + $this->api = $api; + $this->home = $api->dir . $this->ID; + $this->urlbase = $api->url . $this->ID . '/'; } - return true; - } - - /** - * Register a callback function for a specific (server-side) hook - * - * @param string $hook Hook name - * @param mixed $callback Callback function as string or array with object reference and method name - */ - public function add_hook($hook, $callback) - { - $this->api->register_hook($hook, $callback); - } - - /** - * Unregister a callback function for a specific (server-side) hook. - * - * @param string $hook Hook name - * @param mixed $callback Callback function as string or array with object reference and method name - */ - public function remove_hook($hook, $callback) - { - $this->api->unregister_hook($hook, $callback); - } - - /** - * Load localized texts from the plugins dir - * - * @param string $dir Directory to search in - * @param mixed $add2client Make texts also available on the client (array with list or true for all) - */ - public function add_texts($dir, $add2client = false) - { - $domain = $this->ID; - $lang = $_SESSION['language']; - $langs = array_unique(array('en_US', $lang)); - $locdir = slashify(realpath(slashify($this->home) . $dir)); - $texts = array(); - - // Language aliases used to find localization in similar lang, see below - $aliases = array( - 'de_CH' => 'de_DE', - 'es_AR' => 'es_ES', - 'fa_AF' => 'fa_IR', - 'nl_BE' => 'nl_NL', - 'pt_BR' => 'pt_PT', - 'zh_CN' => 'zh_TW', - ); - - // use buffering to handle empty lines/spaces after closing PHP tag - ob_start(); - - foreach ($langs as $lng) { - $fpath = $locdir . $lng . '.inc'; - if (is_file($fpath) && is_readable($fpath)) { - include $fpath; - $texts = (array)$labels + (array)$messages + (array)$texts; - } - else if ($lng != 'en_US') { - // Find localization in similar language (#1488401) - $alias = null; - if (!empty($aliases[$lng])) { - $alias = $aliases[$lng]; + /** + * Initialization method, needs to be implemented by the plugin itself + */ + abstract function init(); + + /** + * Attempt to load the given plugin which is required for the current plugin + * + * @param string Plugin name + * @return boolean True on success, false on failure + */ + public function require_plugin($plugin_name) + { + return $this->api->load_plugin($plugin_name); + } + + /** + * Load local config file from plugins directory. + * The loaded values are patched over the global configuration. + * + * @param string $fname Config file name relative to the plugin's folder + * + * @return boolean True on success, false on failure + */ + public function load_config($fname = 'config.inc.php') + { + $fpath = $this->home.'/'.$fname; + $rcube = rcube::get_instance(); + + if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) { + rcube::raise_error(array( + 'code' => 527, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Failed to load config from $fpath"), true, false); + return false; } - else if ($key = array_search($lng, $aliases)) { - $alias = $key; + + return true; + } + + /** + * Register a callback function for a specific (server-side) hook + * + * @param string $hook Hook name + * @param mixed $callback Callback function as string or array + * with object reference and method name + */ + public function add_hook($hook, $callback) + { + $this->api->register_hook($hook, $callback); + } + + /** + * Unregister a callback function for a specific (server-side) hook. + * + * @param string $hook Hook name + * @param mixed $callback Callback function as string or array + * with object reference and method name + */ + public function remove_hook($hook, $callback) + { + $this->api->unregister_hook($hook, $callback); + } + + /** + * Load localized texts from the plugins dir + * + * @param string $dir Directory to search in + * @param mixed $add2client Make texts also available on the client + * (array with list or true for all) + */ + public function add_texts($dir, $add2client = false) + { + $domain = $this->ID; + $lang = $_SESSION['language']; + $langs = array_unique(array('en_US', $lang)); + $locdir = slashify(realpath(slashify($this->home) . $dir)); + $texts = array(); + + // Language aliases used to find localization in similar lang, see below + $aliases = array( + 'de_CH' => 'de_DE', + 'es_AR' => 'es_ES', + 'fa_AF' => 'fa_IR', + 'nl_BE' => 'nl_NL', + 'pt_BR' => 'pt_PT', + 'zh_CN' => 'zh_TW', + ); + + // use buffering to handle empty lines/spaces after closing PHP tag + ob_start(); + + foreach ($langs as $lng) { + $fpath = $locdir . $lng . '.inc'; + if (is_file($fpath) && is_readable($fpath)) { + include $fpath; + $texts = (array)$labels + (array)$messages + (array)$texts; + } + else if ($lng != 'en_US') { + // Find localization in similar language (#1488401) + $alias = null; + if (!empty($aliases[$lng])) { + $alias = $aliases[$lng]; + } + else if ($key = array_search($lng, $aliases)) { + $alias = $key; + } + + if (!empty($alias)) { + $fpath = $locdir . $alias . '.inc'; + if (is_file($fpath) && is_readable($fpath)) { + include $fpath; + $texts = (array)$labels + (array)$messages + (array)$texts; + } + } + } } - if (!empty($alias)) { - $fpath = $locdir . $alias . '.inc'; - if (is_file($fpath) && is_readable($fpath)) { - include $fpath; - $texts = (array)$labels + (array)$messages + (array)$texts; - } + ob_end_clean(); + + // prepend domain to text keys and add to the application texts repository + if (!empty($texts)) { + $add = array(); + foreach ($texts as $key => $value) { + $add[$domain.'.'.$key] = $value; + } + + $rcube = rcube::get_instance(); + $rcube->load_language($lang, $add); + + // add labels to client + if ($add2client) { + if (is_array($add2client)) { + $js_labels = array_map(array($this, 'label_map_callback'), $add2client); + } + else { + $js_labels = array_keys($add); + } + $rcube->output->add_label($js_labels); + } } - } } - ob_end_clean(); + /** + * Wrapper for rcube::gettext() adding the plugin ID as domain + * + * @param string $p Message identifier + * + * @return string Localized text + * @see rcube::gettext() + */ + public function gettext($p) + { + return rcube::get_instance()->gettext($p, $this->ID); + } - // prepend domain to text keys and add to the application texts repository - if (!empty($texts)) { - $add = array(); - foreach ($texts as $key => $value) - $add[$domain.'.'.$key] = $value; + /** + * Register this plugin to be responsible for a specific task + * + * @param string $task Task name (only characters [a-z0-9_.-] are allowed) + */ + public function register_task($task) + { + if ($this->api->register_task($task, $this->ID)) { + $this->mytask = $task; + } + } - $rcube = rcube::get_instance(); - $rcube->load_language($lang, $add); + /** + * Register a handler for a specific client-request action + * + * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction + * + * @param string $action Action name (should be unique) + * @param mixed $callback Callback function as string + * or array with object reference and method name + */ + public function register_action($action, $callback) + { + $this->api->register_action($action, $this->ID, $callback, $this->mytask); + } - // add labels to client - if ($add2client) { - $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add); - $rcube->output->add_label($js_labels); - } + /** + * Register a handler function for a template object + * + * When parsing a template for display, tags like + * will be replaced by the return value if the registered callback function. + * + * @param string $name Object name (should be unique and start with 'plugin.') + * @param mixed $callback Callback function as string or array with object reference + * and method name + */ + public function register_handler($name, $callback) + { + $this->api->register_handler($name, $this->ID, $callback); } - } - - /** - * Wrapper for rcube::gettext() adding the plugin ID as domain - * - * @param string $p Message identifier - * @return string Localized text - * @see rcube::gettext() - */ - public function gettext($p) - { - return rcube::get_instance()->gettext($p, $this->ID); - } - - /** - * Register this plugin to be responsible for a specific task - * - * @param string $task Task name (only characters [a-z0-9_.-] are allowed) - */ - public function register_task($task) - { - if ($this->api->register_task($task, $this->ID)) - $this->mytask = $task; - } - - /** - * Register a handler for a specific client-request action - * - * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction - * - * @param string $action Action name (should be unique) - * @param mixed $callback Callback function as string or array with object reference and method name - */ - public function register_action($action, $callback) - { - $this->api->register_action($action, $this->ID, $callback, $this->mytask); - } - - /** - * Register a handler function for a template object - * - * When parsing a template for display, tags like - * will be replaced by the return value if the registered callback function. - * - * @param string $name Object name (should be unique and start with 'plugin.') - * @param mixed $callback Callback function as string or array with object reference and method name - */ - public function register_handler($name, $callback) - { - $this->api->register_handler($name, $this->ID, $callback); - } - - /** - * Make this javascipt file available on the client - * - * @param string $fn File path; absolute or relative to the plugin directory - */ - public function include_script($fn) - { - $this->api->include_script($this->resource_url($fn)); - } - - /** - * Make this stylesheet available on the client - * - * @param string $fn File path; absolute or relative to the plugin directory - */ - public function include_stylesheet($fn) - { - $this->api->include_stylesheet($this->resource_url($fn)); - } - - /** - * Append a button to a certain container - * - * @param array $p Hash array with named parameters (as used in skin templates) - * @param string $container Container name where the buttons should be added to - * @see rcube_remplate::button() - */ - public function add_button($p, $container) - { - if ($this->api->output->type == 'html') { - // fix relative paths - foreach (array('imagepas', 'imageact', 'imagesel') as $key) - if ($p[$key]) - $p[$key] = $this->api->url . $this->resource_url($p[$key]); - - $this->api->add_content($this->api->output->button($p), $container); + + /** + * Make this javascipt file available on the client + * + * @param string $fn File path; absolute or relative to the plugin directory + */ + public function include_script($fn) + { + $this->api->include_script($this->resource_url($fn)); } - } - - /** - * Generate an absolute URL to the given resource within the current - * plugin directory - * - * @param string $fn The file name - * @return string Absolute URL to the given resource - */ - public function url($fn) - { - return $this->api->url . $this->resource_url($fn); - } - - /** - * Make the given file name link into the plugin directory - * - * @param string $fn Filename - */ - private function resource_url($fn) - { - if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) - return $this->ID . '/' . $fn; - else - return $fn; - } - - /** - * Provide path to the currently selected skin folder within the plugin directory - * with a fallback to the default skin folder. - * - * @return string Skin path relative to plugins directory - */ - public function local_skin_path() - { - $rcube = rcube::get_instance(); - foreach (array($rcube->config->get('skin'), 'larry') as $skin) { - $skin_path = 'skins/' . $skin; - if (is_dir(realpath(slashify($this->home) . $skin_path))) - break; + + /** + * Make this stylesheet available on the client + * + * @param string $fn File path; absolute or relative to the plugin directory + */ + public function include_stylesheet($fn) + { + $this->api->include_stylesheet($this->resource_url($fn)); } - return $skin_path; - } + /** + * Append a button to a certain container + * + * @param array $p Hash array with named parameters (as used in skin templates) + * @param string $container Container name where the buttons should be added to + * + * @see rcube_remplate::button() + */ + public function add_button($p, $container) + { + if ($this->api->output->type == 'html') { + // fix relative paths + foreach (array('imagepas', 'imageact', 'imagesel') as $key) { + if ($p[$key]) { + $p[$key] = $this->api->url . $this->resource_url($p[$key]); + } + } + + $this->api->add_content($this->api->output->button($p), $container); + } + } - /** - * Callback function for array_map - * - * @param string $key Array key. - * @return string - */ - private function label_map_callback($key) - { - return $this->ID.'.'.$key; - } + /** + * Generate an absolute URL to the given resource within the current + * plugin directory + * + * @param string $fn The file name + * + * @return string Absolute URL to the given resource + */ + public function url($fn) + { + return $this->api->url . $this->resource_url($fn); + } + /** + * Make the given file name link into the plugin directory + * + * @param string $fn Filename + */ + private function resource_url($fn) + { + if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) { + return $this->ID . '/' . $fn; + } + else { + return $fn; + } + } + + /** + * Provide path to the currently selected skin folder within the plugin directory + * with a fallback to the default skin folder. + * + * @return string Skin path relative to plugins directory + */ + public function local_skin_path() + { + $rcube = rcube::get_instance(); + foreach (array($rcube->config->get('skin'), 'larry') as $skin) { + $skin_path = 'skins/' . $skin; + if (is_dir(realpath(slashify($this->home) . $skin_path))) { + break; + } + } + + return $skin_path; + } + + /** + * Callback function for array_map + * + * @param string $key Array key. + * @return string + */ + private function label_map_callback($key) + { + return $this->ID.'.'.$key; + } } diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php index b4626441d..8a4cce215 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -3,7 +3,7 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2011, The Roundcube Dev Team | + | Copyright (C) 2008-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -17,9 +17,9 @@ */ // location where plugins are loade from -if (!defined('RCUBE_PLUGINS_DIR')) - define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/'); - +if (!defined('RCUBE_PLUGINS_DIR')) { + define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/'); +} /** * The plugin loader and global API @@ -29,469 +29,476 @@ if (!defined('RCUBE_PLUGINS_DIR')) */ class rcube_plugin_api { - static protected $instance; - - public $dir; - public $url = 'plugins/'; - public $task = ''; - public $output; - - public $handlers = array(); - protected $plugins = array(); - protected $tasks = array(); - protected $actions = array(); - protected $actionmap = array(); - protected $objectsmap = array(); - protected $template_contents = array(); - protected $active_hook = false; - - // Deprecated names of hooks, will be removed after 0.5-stable release - protected $deprecated_hooks = array( - 'create_user' => 'user_create', - 'kill_session' => 'session_destroy', - 'upload_attachment' => 'attachment_upload', - 'save_attachment' => 'attachment_save', - 'get_attachment' => 'attachment_get', - 'cleanup_attachments' => 'attachments_cleanup', - 'display_attachment' => 'attachment_display', - 'remove_attachment' => 'attachment_delete', - 'outgoing_message_headers' => 'message_outgoing_headers', - 'outgoing_message_body' => 'message_outgoing_body', - 'address_sources' => 'addressbooks_list', - 'get_address_book' => 'addressbook_get', - 'create_contact' => 'contact_create', - 'save_contact' => 'contact_update', - 'contact_save' => 'contact_update', - 'delete_contact' => 'contact_delete', - 'manage_folders' => 'folders_list', - 'list_mailboxes' => 'mailboxes_list', - 'save_preferences' => 'preferences_save', - 'user_preferences' => 'preferences_list', - 'list_prefs_sections' => 'preferences_sections_list', - 'list_identities' => 'identities_list', - 'create_identity' => 'identity_create', - 'delete_identity' => 'identity_delete', - 'save_identity' => 'identity_update', - 'identity_save' => 'identity_update', - // to be removed after 0.8 - 'imap_init' => 'storage_init', - 'mailboxes_list' => 'storage_folders', - 'imap_connect' => 'storage_connect', - ); - - /** - * This implements the 'singleton' design pattern - * - * @return rcube_plugin_api The one and only instance if this class - */ - static function get_instance() - { - if (!self::$instance) { - self::$instance = new rcube_plugin_api(); - } + static protected $instance; + + public $dir; + public $url = 'plugins/'; + public $task = ''; + public $output; + public $handlers = array(); + + protected $plugins = array(); + protected $tasks = array(); + protected $actions = array(); + protected $actionmap = array(); + protected $objectsmap = array(); + protected $template_contents = array(); + protected $active_hook = false; + + // Deprecated names of hooks, will be removed after 0.5-stable release + protected $deprecated_hooks = array( + 'create_user' => 'user_create', + 'kill_session' => 'session_destroy', + 'upload_attachment' => 'attachment_upload', + 'save_attachment' => 'attachment_save', + 'get_attachment' => 'attachment_get', + 'cleanup_attachments' => 'attachments_cleanup', + 'display_attachment' => 'attachment_display', + 'remove_attachment' => 'attachment_delete', + 'outgoing_message_headers' => 'message_outgoing_headers', + 'outgoing_message_body' => 'message_outgoing_body', + 'address_sources' => 'addressbooks_list', + 'get_address_book' => 'addressbook_get', + 'create_contact' => 'contact_create', + 'save_contact' => 'contact_update', + 'contact_save' => 'contact_update', + 'delete_contact' => 'contact_delete', + 'manage_folders' => 'folders_list', + 'list_mailboxes' => 'mailboxes_list', + 'save_preferences' => 'preferences_save', + 'user_preferences' => 'preferences_list', + 'list_prefs_sections' => 'preferences_sections_list', + 'list_identities' => 'identities_list', + 'create_identity' => 'identity_create', + 'delete_identity' => 'identity_delete', + 'save_identity' => 'identity_update', + 'identity_save' => 'identity_update', + // to be removed after 0.8 + 'imap_init' => 'storage_init', + 'mailboxes_list' => 'storage_folders', + 'imap_connect' => 'storage_connect', + ); + + /** + * This implements the 'singleton' design pattern + * + * @return rcube_plugin_api The one and only instance if this class + */ + static function get_instance() + { + if (!self::$instance) { + self::$instance = new rcube_plugin_api(); + } - return self::$instance; - } - - - /** - * Private constructor - */ - protected function __construct() - { - $this->dir = slashify(RCUBE_PLUGINS_DIR); - } - - - /** - * Initialize plugin engine - * - * This has to be done after rcmail::load_gui() or rcmail::json_init() - * was called because plugins need to have access to rcmail->output - * - * @param object rcube Instance of the rcube base class - * @param string Current application task (used for conditional plugin loading) - */ - public function init($app, $task = '') - { - $this->task = $task; - $this->output = $app->output; - - // register an internal hook - $this->register_hook('template_container', array($this, 'template_container_hook')); - - // maybe also register a shudown function which triggers shutdown functions of all plugin objects - } - - - /** - * Load and init all enabled plugins - * - * This has to be done after rcmail::load_gui() or rcmail::json_init() - * was called because plugins need to have access to rcmail->output - * - * @param array List of configured plugins to load - * @param array List of plugins required by the application - */ - public function load_plugins($plugins_enabled, $required_plugins = array()) - { - foreach ($plugins_enabled as $plugin_name) { - $this->load_plugin($plugin_name); + return self::$instance; } - // check existance of all required core plugins - foreach ($required_plugins as $plugin_name) { - $loaded = false; - foreach ($this->plugins as $plugin) { - if ($plugin instanceof $plugin_name) { - $loaded = true; - break; - } - } - - // load required core plugin if no derivate was found - if (!$loaded) - $loaded = $this->load_plugin($plugin_name); - - // trigger fatal error if still not loaded - if (!$loaded) { - rcube::raise_error(array('code' => 520, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Requried plugin $plugin_name was not loaded"), true, true); - } + /** + * Private constructor + */ + protected function __construct() + { + $this->dir = slashify(RCUBE_PLUGINS_DIR); } - } - - /** - * Load the specified plugin - * - * @param string Plugin name - * @return boolean True on success, false if not loaded or failure - */ - public function load_plugin($plugin_name) - { - static $plugins_dir; - - if (!$plugins_dir) { - $dir = dir($this->dir); - $plugins_dir = unslashify($dir->path); + + /** + * Initialize plugin engine + * + * This has to be done after rcmail::load_gui() or rcmail::json_init() + * was called because plugins need to have access to rcmail->output + * + * @param object rcube Instance of the rcube base class + * @param string Current application task (used for conditional plugin loading) + */ + public function init($app, $task = '') + { + $this->task = $task; + $this->output = $app->output; + + // register an internal hook + $this->register_hook('template_container', array($this, 'template_container_hook')); + + // maybe also register a shudown function which triggers + // shutdown functions of all plugin objects } - // plugin already loaded - if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) - return true; - - $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; - - if (file_exists($fn)) { - include($fn); - - // instantiate class if exists - if (class_exists($plugin_name, false)) { - $plugin = new $plugin_name($this); - // check inheritance... - if (is_subclass_of($plugin, 'rcube_plugin')) { - // ... task, request type and framed mode - if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task)) - && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html')) - && (!$plugin->noframe || empty($_REQUEST['_framed'])) - ) { - $plugin->init(); - $this->plugins[$plugin_name] = $plugin; - } - return true; + /** + * Load and init all enabled plugins + * + * This has to be done after rcmail::load_gui() or rcmail::json_init() + * was called because plugins need to have access to rcmail->output + * + * @param array List of configured plugins to load + * @param array List of plugins required by the application + */ + public function load_plugins($plugins_enabled, $required_plugins = array()) + { + foreach ($plugins_enabled as $plugin_name) { + $this->load_plugin($plugin_name); + } + + // check existance of all required core plugins + foreach ($required_plugins as $plugin_name) { + $loaded = false; + foreach ($this->plugins as $plugin) { + if ($plugin instanceof $plugin_name) { + $loaded = true; + break; + } + } + + // load required core plugin if no derivate was found + if (!$loaded) { + $loaded = $this->load_plugin($plugin_name); + } + + // trigger fatal error if still not loaded + if (!$loaded) { + rcube::raise_error(array( + 'code' => 520, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Requried plugin $plugin_name was not loaded"), true, true); + } } - } - else { - rcube::raise_error(array('code' => 520, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "No plugin class $plugin_name found in $fn"), true, false); - } } - else { - rcube::raise_error(array('code' => 520, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Failed to load plugin file $fn"), true, false); + + /** + * Load the specified plugin + * + * @param string Plugin name + * + * @return boolean True on success, false if not loaded or failure + */ + public function load_plugin($plugin_name) + { + static $plugins_dir; + + if (!$plugins_dir) { + $dir = dir($this->dir); + $plugins_dir = unslashify($dir->path); + } + + // plugin already loaded + if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) { + return true; + } + + $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name + . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + + if (file_exists($fn)) { + include $fn; + + // instantiate class if exists + if (class_exists($plugin_name, false)) { + $plugin = new $plugin_name($this); + // check inheritance... + if (is_subclass_of($plugin, 'rcube_plugin')) { + // ... task, request type and framed mode + if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task)) + && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html')) + && (!$plugin->noframe || empty($_REQUEST['_framed'])) + ) { + $plugin->init(); + $this->plugins[$plugin_name] = $plugin; + } + return true; + } + } + else { + rcube::raise_error(array('code' => 520, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "No plugin class $plugin_name found in $fn"), + true, false); + } + } + else { + rcube::raise_error(array('code' => 520, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Failed to load plugin file $fn"), true, false); + } + + return false; } - return false; - } - - - /** - * Allows a plugin object to register a callback for a certain hook - * - * @param string $hook Hook name - * @param mixed $callback String with global function name or array($obj, 'methodname') - */ - public function register_hook($hook, $callback) - { - if (is_callable($callback)) { - if (isset($this->deprecated_hooks[$hook])) { - rcube::raise_error(array('code' => 522, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false); - $hook = $this->deprecated_hooks[$hook]; - } - $this->handlers[$hook][] = $callback; + /** + * Allows a plugin object to register a callback for a certain hook + * + * @param string $hook Hook name + * @param mixed $callback String with global function name or array($obj, 'methodname') + */ + public function register_hook($hook, $callback) + { + if (is_callable($callback)) { + if (isset($this->deprecated_hooks[$hook])) { + rcube::raise_error(array('code' => 522, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Deprecated hook name. " + . $hook . ' -> ' . $this->deprecated_hooks[$hook]), true, false); + $hook = $this->deprecated_hooks[$hook]; + } + $this->handlers[$hook][] = $callback; + } + else { + rcube::raise_error(array('code' => 521, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Invalid callback function for $hook"), true, false); + } } - else - rcube::raise_error(array('code' => 521, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Invalid callback function for $hook"), true, false); - } - - /** - * Allow a plugin object to unregister a callback. - * - * @param string $hook Hook name - * @param mixed $callback String with global function name or array($obj, 'methodname') - */ - public function unregister_hook($hook, $callback) - { - $callback_id = array_search($callback, $this->handlers[$hook]); - if ($callback_id !== false) { - unset($this->handlers[$hook][$callback_id]); + + /** + * Allow a plugin object to unregister a callback. + * + * @param string $hook Hook name + * @param mixed $callback String with global function name or array($obj, 'methodname') + */ + public function unregister_hook($hook, $callback) + { + $callback_id = array_search($callback, $this->handlers[$hook]); + if ($callback_id !== false) { + unset($this->handlers[$hook][$callback_id]); + } } - } - - - /** - * Triggers a plugin hook. - * This is called from the application and executes all registered handlers - * - * @param string $hook Hook name - * @param array $args Named arguments (key->value pairs) - * @return array The (probably) altered hook arguments - */ - public function exec_hook($hook, $args = array()) - { - if (!is_array($args)) - $args = array('arg' => $args); - - $args += array('abort' => false); - $this->active_hook = $hook; - - foreach ((array)$this->handlers[$hook] as $callback) { - $ret = call_user_func($callback, $args); - if ($ret && is_array($ret)) - $args = $ret + $args; - - if ($args['abort']) - break; + + /** + * Triggers a plugin hook. + * This is called from the application and executes all registered handlers + * + * @param string $hook Hook name + * @param array $args Named arguments (key->value pairs) + * + * @return array The (probably) altered hook arguments + */ + public function exec_hook($hook, $args = array()) + { + if (!is_array($args)) { + $args = array('arg' => $args); + } + + $args += array('abort' => false); + $this->active_hook = $hook; + + foreach ((array)$this->handlers[$hook] as $callback) { + $ret = call_user_func($callback, $args); + if ($ret && is_array($ret)) { + $args = $ret + $args; + } + + if ($args['abort']) { + break; + } + } + + $this->active_hook = false; + return $args; } - $this->active_hook = false; - return $args; - } - - - /** - * Let a plugin register a handler for a specific request - * - * @param string $action Action name (_task=mail&_action=plugin.foo) - * @param string $owner Plugin name that registers this action - * @param mixed $callback Callback: string with global function name or array($obj, 'methodname') - * @param string $task Task name registered by this plugin - */ - public function register_action($action, $owner, $callback, $task = null) - { - // check action name - if ($task) - $action = $task.'.'.$action; - else if (strpos($action, 'plugin.') !== 0) - $action = 'plugin.'.$action; - - // can register action only if it's not taken or registered by myself - if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) { - $this->actions[$action] = $callback; - $this->actionmap[$action] = $owner; + /** + * Let a plugin register a handler for a specific request + * + * @param string $action Action name (_task=mail&_action=plugin.foo) + * @param string $owner Plugin name that registers this action + * @param mixed $callback Callback: string with global function name or array($obj, 'methodname') + * @param string $task Task name registered by this plugin + */ + public function register_action($action, $owner, $callback, $task = null) + { + // check action name + if ($task) + $action = $task.'.'.$action; + else if (strpos($action, 'plugin.') !== 0) + $action = 'plugin.'.$action; + + // can register action only if it's not taken or registered by myself + if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) { + $this->actions[$action] = $callback; + $this->actionmap[$action] = $owner; + } + else { + rcube::raise_error(array('code' => 523, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Cannot register action $action;" + ." already taken by another plugin"), true, false); + } } - else { - rcube::raise_error(array('code' => 523, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Cannot register action $action; already taken by another plugin"), true, false); + + /** + * This method handles requests like _task=mail&_action=plugin.foo + * It executes the callback function that was registered with the given action. + * + * @param string $action Action name + */ + public function exec_action($action) + { + if (isset($this->actions[$action])) { + call_user_func($this->actions[$action]); + } + else if (rcube::get_instance()->action != 'refresh') { + rcube::raise_error(array('code' => 524, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "No handler found for action $action"), true, true); + } } - } - - - /** - * This method handles requests like _task=mail&_action=plugin.foo - * It executes the callback function that was registered with the given action. - * - * @param string $action Action name - */ - public function exec_action($action) - { - if (isset($this->actions[$action])) { - call_user_func($this->actions[$action]); + + /** + * Register a handler function for template objects + * + * @param string $name Object name + * @param string $owner Plugin name that registers this action + * @param mixed $callback Callback: string with global function name or array($obj, 'methodname') + */ + public function register_handler($name, $owner, $callback) + { + // check name + if (strpos($name, 'plugin.') !== 0) { + $name = 'plugin.' . $name; + } + + // can register handler only if it's not taken or registered by myself + if (is_object($this->output) + && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) + ) { + $this->output->add_handler($name, $callback); + $this->objectsmap[$name] = $owner; + } + else { + rcube::raise_error(array('code' => 525, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Cannot register template handler $name;" + ." already taken by another plugin or no output object available"), true, false); + } } - else if (rcube::get_instance()->action != 'refresh') { - rcube::raise_error(array('code' => 524, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "No handler found for action $action"), true, true); + + /** + * Register this plugin to be responsible for a specific task + * + * @param string $task Task name (only characters [a-z0-9_.-] are allowed) + * @param string $owner Plugin name that registers this action + */ + public function register_task($task, $owner) + { + // tasks are irrelevant in framework mode + if (!class_exists('rcmail', false)) { + return true; + } + + if ($task != asciiwords($task)) { + rcube::raise_error(array('code' => 526, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Invalid task name: $task." + ." Only characters [a-z0-9_.-] are allowed"), true, false); + } + else if (in_array($task, rcmail::$main_tasks)) { + rcube::raise_error(array('code' => 526, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Cannot register taks $task;" + ." already taken by another plugin or the application itself"), true, false); + } + else { + $this->tasks[$task] = $owner; + rcmail::$main_tasks[] = $task; + return true; + } + + return false; } - } - - - /** - * Register a handler function for template objects - * - * @param string $name Object name - * @param string $owner Plugin name that registers this action - * @param mixed $callback Callback: string with global function name or array($obj, 'methodname') - */ - public function register_handler($name, $owner, $callback) - { - // check name - if (strpos($name, 'plugin.') !== 0) - $name = 'plugin.'.$name; - - // can register handler only if it's not taken or registered by myself - if (is_object($this->output) && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)) { - $this->output->add_handler($name, $callback); - $this->objectsmap[$name] = $owner; + + /** + * Checks whether the given task is registered by a plugin + * + * @param string $task Task name + * + * @return boolean True if registered, otherwise false + */ + public function is_plugin_task($task) + { + return $this->tasks[$task] ? true : false; } - else { - rcube::raise_error(array('code' => 525, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false); + + /** + * Check if a plugin hook is currently processing. + * Mainly used to prevent loops and recursion. + * + * @param string $hook Hook to check (optional) + * + * @return boolean True if any/the given hook is currently processed, otherwise false + */ + public function is_processing($hook = null) + { + return $this->active_hook && (!$hook || $this->active_hook == $hook); } - } - - - /** - * Register this plugin to be responsible for a specific task - * - * @param string $task Task name (only characters [a-z0-9_.-] are allowed) - * @param string $owner Plugin name that registers this action - */ - public function register_task($task, $owner) - { - // tasks are irrelevant in framework mode - if (!class_exists('rcmail', false)) - return true; - - if ($task != asciiwords($task)) { - rcube::raise_error(array('code' => 526, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false); + + /** + * Include a plugin script file in the current HTML page + * + * @param string $fn Path to script + */ + public function include_script($fn) + { + if (is_object($this->output) && $this->output->type == 'html') { + $src = $this->resource_url($fn); + $this->output->add_header(html::tag('script', + array('type' => "text/javascript", 'src' => $src))); + } } - else if (in_array($task, rcmail::$main_tasks)) { - rcube::raise_error(array('code' => 526, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false); + + /** + * Include a plugin stylesheet in the current HTML page + * + * @param string $fn Path to stylesheet + */ + public function include_stylesheet($fn) + { + if (is_object($this->output) && $this->output->type == 'html') { + $src = $this->resource_url($fn); + $this->output->include_css($src); + } } - else { - $this->tasks[$task] = $owner; - rcmail::$main_tasks[] = $task; - return true; + + /** + * Save the given HTML content to be added to a template container + * + * @param string $html HTML content + * @param string $container Template container identifier + */ + public function add_content($html, $container) + { + $this->template_contents[$container] .= $html . "\n"; } - return false; - } - - - /** - * Checks whether the given task is registered by a plugin - * - * @param string $task Task name - * @return boolean True if registered, otherwise false - */ - public function is_plugin_task($task) - { - return $this->tasks[$task] ? true : false; - } - - - /** - * Check if a plugin hook is currently processing. - * Mainly used to prevent loops and recursion. - * - * @param string $hook Hook to check (optional) - * @return boolean True if any/the given hook is currently processed, otherwise false - */ - public function is_processing($hook = null) - { - return $this->active_hook && (!$hook || $this->active_hook == $hook); - } - - /** - * Include a plugin script file in the current HTML page - * - * @param string $fn Path to script - */ - public function include_script($fn) - { - if (is_object($this->output) && $this->output->type == 'html') { - $src = $this->resource_url($fn); - $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src))); + /** + * Returns list of loaded plugins names + * + * @return array List of plugin names + */ + public function loaded_plugins() + { + return array_keys($this->plugins); } - } - - - /** - * Include a plugin stylesheet in the current HTML page - * - * @param string $fn Path to stylesheet - */ - public function include_stylesheet($fn) - { - if (is_object($this->output) && $this->output->type == 'html') { - $src = $this->resource_url($fn); - $this->output->include_css($src); + + /** + * Callback for template_container hooks + * + * @param array $attrib + * @return array + */ + protected function template_container_hook($attrib) + { + $container = $attrib['name']; + return array('content' => $attrib['content'] . $this->template_contents[$container]); } - } - - - /** - * Save the given HTML content to be added to a template container - * - * @param string $html HTML content - * @param string $container Template container identifier - */ - public function add_content($html, $container) - { - $this->template_contents[$container] .= $html . "\n"; - } - - - /** - * Returns list of loaded plugins names - * - * @return array List of plugin names - */ - public function loaded_plugins() - { - return array_keys($this->plugins); - } - - - /** - * Callback for template_container hooks - * - * @param array $attrib - * @return array - */ - protected function template_container_hook($attrib) - { - $container = $attrib['name']; - return array('content' => $attrib['content'] . $this->template_contents[$container]); - } - - - /** - * Make the given file name link into the plugins directory - * - * @param string $fn Filename - * @return string - */ - protected function resource_url($fn) - { - if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) - return $this->url . $fn; - else - return $fn; - } + /** + * Make the given file name link into the plugins directory + * + * @param string $fn Filename + * @return string + */ + protected function resource_url($fn) + { + if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) + return $this->url . $fn; + else + return $fn; + } } -- cgit v1.2.3 From 679b375a4685ad84456860accdd6d719531c81cf Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 21 Dec 2012 09:50:08 +0100 Subject: Fix comment --- program/lib/Roundcube/rcube_db.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 9283bb0d4..3e4617948 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -426,7 +426,7 @@ class rcube_db * * @param mixed $result Optional query handle * - * @return int Number of rows or false on failure + * @return int Number of (matching) rows */ public function affected_rows($result = null) { -- cgit v1.2.3 From 7d88e614aec9d6dd3bfb6b85c774e2a53b741948 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sat, 22 Dec 2012 15:42:14 +0100 Subject: Add hint about possible disabled fsockopen() function on connection error --- program/lib/Roundcube/rcube_imap_generic.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 112e91350..59a444da7 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -753,12 +753,16 @@ class rcube_imap_generic $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); if (!$this->fp) { + if (!$errstr) { + $errstr = "Unknown reason (fsockopen() function disabled?)"; + } $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr)); return false; } - if ($this->prefs['timeout'] > 0) + if ($this->prefs['timeout'] > 0) { stream_set_timeout($this->fp, $this->prefs['timeout']); + } $line = trim(fgets($this->fp, 8192)); -- cgit v1.2.3 From 0931a97c5fc7231df99fdf4cdeebb525392886ed Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 23 Dec 2012 15:09:56 +0100 Subject: Fix handling of parentheses in URLs --- program/lib/Roundcube/rcube_string_replacer.php | 27 ++++++++++++++++++++++++- tests/Framework/StringReplacer.php | 6 ++++++ 2 files changed, 32 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php index 0fe982b26..6b289886b 100644 --- a/program/lib/Roundcube/rcube_string_replacer.php +++ b/program/lib/Roundcube/rcube_string_replacer.php @@ -36,7 +36,7 @@ class rcube_string_replacer // Support unicode/punycode in top-level domain part $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})'; $url1 = '.:;,'; - $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-'; + $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]\\(\\){}\*-'; $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/"; $this->mailto_pattern = "/(" @@ -161,6 +161,9 @@ class rcube_string_replacer // "http://example.com/?a[b]=c". However we need to handle // properly situation when a bracket is placed at the end // of the link e.g. "[http://example.com]" + // Yes, this is not perfect handles correctly only paired characters + // but it should work for common cases + if (preg_match('/(\\[|\\])/', $url)) { $in = false; for ($i=0, $len=strlen($url); $i<$len; $i++) { @@ -182,6 +185,28 @@ class rcube_string_replacer } } + // Do the same for parentheses + if (preg_match('/(\\(|\\))/', $url)) { + $in = false; + for ($i=0, $len=strlen($url); $i<$len; $i++) { + if ($url[$i] == '(') { + if ($in) + break; + $in = true; + } + else if ($url[$i] == ')') { + if (!$in) + break; + $in = false; + } + } + + if ($i < $len) { + $suffix = substr($url, $i); + $url = substr($url, 0, $i); + } + } + return $suffix; } } diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php index a76ba00ee..60399cf6b 100644 --- a/tests/Framework/StringReplacer.php +++ b/tests/Framework/StringReplacer.php @@ -29,6 +29,12 @@ class Framework_StringReplacer extends PHPUnit_Framework_TestCase array('Start http://localhost/?foo End', 'Start http://localhost/?foo End'), array('www.domain.tld', 'www.domain.tld'), array('WWW.DOMAIN.TLD', 'WWW.DOMAIN.TLD'), + array('[http://link.com]', '[http://link.com]'), + array('http://link.com?a[]=1', 'http://link.com?a[]=1'), + array('http://link.com?a[]', 'http://link.com?a[]'), + array('(http://link.com)', '(http://link.com)'), + array('http://link.com?a(b)c', 'http://link.com?a(b)c'), + array('http://link.com?(link)', 'http://link.com?(link)'), ); } -- cgit v1.2.3 From 7ac94421bf85eb04c00c5ed05390e1ea0c6bcb0b Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 25 Dec 2012 18:06:17 +0100 Subject: Move washtml class into Roundcube Framework (rcube_washtml), add some improvements --- program/include/bc.php | 4 + program/lib/Roundcube/rcube_washtml.php | 451 ++++++++++++++++++++++++++++++++ program/lib/washtml.php | 330 ----------------------- program/steps/mail/func.inc | 68 +---- tests/Framework/Washtml.php | 28 ++ tests/MailFunc.php | 2 +- tests/phpunit.xml | 1 + 7 files changed, 486 insertions(+), 398 deletions(-) create mode 100644 program/lib/Roundcube/rcube_washtml.php delete mode 100644 program/lib/washtml.php create mode 100644 tests/Framework/Washtml.php (limited to 'program/lib') diff --git a/program/include/bc.php b/program/include/bc.php index dc4d54fd7..05d15b9e3 100644 --- a/program/include/bc.php +++ b/program/include/bc.php @@ -408,3 +408,7 @@ function enriched_to_html($data) class rcube_html_page extends rcmail_html_page { } + +class washtml extends rcube_washtml +{ +} diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php new file mode 100644 index 000000000..715c46047 --- /dev/null +++ b/program/lib/Roundcube/rcube_washtml.php @@ -0,0 +1,451 @@ + | + | Author: Aleksander Machniak | + | Author: Frederic Motte | + +-----------------------------------------------------------------------+ + */ + +/** + * Washtml, a HTML sanityzer. + * + * Copyright (c) 2007 Frederic Motte + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * OVERVIEW: + * + * Wahstml take an untrusted HTML and return a safe html string. + * + * SYNOPSIS: + * + * $washer = new washtml($config); + * $washer->wash($html); + * It return a sanityzed string of the $html parameter without html and head tags. + * $html is a string containing the html code to wash. + * $config is an array containing options: + * $config['allow_remote'] is a boolean to allow link to remote images. + * $config['blocked_src'] string with image-src to be used for blocked remote images + * $config['show_washed'] is a boolean to include washed out attributes as x-washed + * $config['cid_map'] is an array where cid urls index urls to replace them. + * $config['charset'] is a string containing the charset of the HTML document if it is not defined in it. + * $washer->extlinks is a reference to a boolean that is set to true if remote images were removed. (FE: show remote images link) + * + * INTERNALS: + * + * Only tags and attributes in the static lists $html_elements and $html_attributes + * are kept, inline styles are also filtered: all style identifiers matching + * /[a-z\-]/i are allowed. Values matching colors, sizes, /[a-z\-]/i and safe + * urls if allowed and cid urls if mapped are kept. + * + * Roundcube Changes: + * - added $block_elements + * - changed $ignore_elements behaviour + * - added RFC2397 support + * - base URL support + * - invalid HTML comments removal before parsing + * - "fixing" unitless CSS values for XHTML output + * - base url resolving + */ + +/** + * Utility class providing HTML sanityzer + * + * @package Framework + * @subpackage Utils + */ +class rcube_washtml +{ + /* Allowed HTML elements (default) */ + static $html_elements = array('a', 'abbr', 'acronym', 'address', 'area', 'b', + 'basefont', 'bdo', 'big', 'blockquote', 'br', 'caption', 'center', + 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', + 'dt', 'em', 'fieldset', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', + 'ins', 'label', 'legend', 'li', 'map', 'menu', 'nobr', 'ol', 'p', 'pre', 'q', + 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table', + 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'wbr', 'img', + // form elements + 'button', 'input', 'textarea', 'select', 'option', 'optgroup' + ); + + /* Ignore these HTML tags and their content */ + static $ignore_elements = array('script', 'applet', 'embed', 'object', 'style'); + + /* Allowed HTML attributes */ + static $html_attribs = array('name', 'class', 'title', 'alt', 'width', 'height', + 'align', 'nowrap', 'col', 'row', 'id', 'rowspan', 'colspan', 'cellspacing', + 'cellpadding', 'valign', 'bgcolor', 'color', 'border', 'bordercolorlight', + 'bordercolordark', 'face', 'marginwidth', 'marginheight', 'axis', 'border', + 'abbr', 'char', 'charoff', 'clear', 'compact', 'coords', 'vspace', 'hspace', + 'cellborder', 'size', 'lang', 'dir', 'usemap', 'shape', 'media', + // attributes of form elements + 'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value' + ); + + /* Block elements which could be empty but cannot be returned in short form () */ + static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center', + 'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong', + 'i', 'b', 'u', 'span', + ); + + /* State for linked objects in HTML */ + public $extlinks = false; + + /* Current settings */ + private $config = array(); + + /* Registered callback functions for tags */ + private $handlers = array(); + + /* Allowed HTML elements */ + private $_html_elements = array(); + + /* Ignore these HTML tags but process their content */ + private $_ignore_elements = array(); + + /* Block elements which could be empty but cannot be returned in short form () */ + private $_block_elements = array(); + + /* Allowed HTML attributes */ + private $_html_attribs = array(); + + + /** + * Class constructor + */ + public function __construct($p = array()) + { + $this->_html_elements = array_flip((array)$p['html_elements']) + array_flip(self::$html_elements) ; + $this->_html_attribs = array_flip((array)$p['html_attribs']) + array_flip(self::$html_attribs); + $this->_ignore_elements = array_flip((array)$p['ignore_elements']) + array_flip(self::$ignore_elements); + $this->_block_elements = array_flip((array)$p['block_elements']) + array_flip(self::$block_elements); + + unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']); + + $this->config = $p + array('show_washed' => true, 'allow_remote' => false, 'cid_map' => array()); + } + + /** + * Register a callback function for a certain tag + */ + public function add_callback($tagName, $callback) + { + $this->handlers[$tagName] = $callback; + } + + /** + * Check CSS style + */ + private function wash_style($style) + { + $s = ''; + + foreach (explode(';', $style) as $declaration) { + if (preg_match('/^\s*([a-z\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) { + $cssid = $match[1]; + $str = $match[2]; + $value = ''; + + while (sizeof($str) > 0 && + preg_match('/^(url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)'./*1,2*/ + '|rgb\(\s*[0-9]+\s*,\s*[0-9]+\s*,\s*[0-9]+\s*\)'. + '|-?[0-9.]+\s*(em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)?'. + '|#[0-9a-f]{3,6}'. + '|[a-z0-9", -]+'. + ')\s*/i', $str, $match) + ) { + if ($match[2]) { + if (($src = $this->config['cid_map'][$match[2]]) + || ($src = $this->config['cid_map'][$this->config['base_url'].$match[2]]) + ) { + $value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')'; + } + else if (preg_match('!^(https?:)?//[a-z0-9/._+-]+$!i', $match[2], $url)) { + if ($this->config['allow_remote']) { + $value .= ' url('.htmlspecialchars($url[0], ENT_QUOTES).')'; + } + else { + $this->extlinks = true; + } + } + else if (preg_match('/^data:.+/i', $match[2])) { // RFC2397 + $value .= ' url('.htmlspecialchars($match[2], ENT_QUOTES).')'; + } + } + else { + // whitelist ? + $value .= ' ' . $match[0]; + + // #1488535: Fix size units, so width:800 would be changed to width:800px + if (preg_match('/(left|right|top|bottom|width|height)/i', $cssid) + && preg_match('/^[0-9]+$/', $match[0]) + ) { + $value .= 'px'; + } + } + + $str = substr($str, strlen($match[0])); + } + + if (isset($value[0])) { + $s .= ($s?' ':'') . $cssid . ':' . $value . ';'; + } + } + } + + return $s; + } + + /** + * Take a node and return allowed attributes and check values + */ + private function wash_attribs($node) + { + $t = ''; + $washed = ''; + + foreach ($node->attributes as $key => $plop) { + $key = strtolower($key); + $value = $node->getAttribute($key); + + if (isset($this->_html_attribs[$key]) || + ($key == 'href' && !preg_match('!^(javascript|vbscript|data:text)!i', $value) + && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value)) + ) { + $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"'; + } + else if ($key == 'style' && ($style = $this->wash_style($value))) { + $quot = strpos($style, '"') !== false ? "'" : '"'; + $t .= ' style=' . $quot . $style . $quot; + } + else if ($key == 'background' || ($key == 'src' && strtolower($node->tagName) == 'img')) { //check tagName anyway + if (($src = $this->config['cid_map'][$value]) + || ($src = $this->config['cid_map'][$this->config['base_url'].$value]) + ) { + $t .= ' ' . $key . '="' . htmlspecialchars($src, ENT_QUOTES) . '"'; + } + else if (preg_match('/^(http|https|ftp):.+/i', $value)) { + if ($this->config['allow_remote']) { + $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"'; + } + else { + $this->extlinks = true; + if ($this->config['blocked_src']) { + $t .= ' ' . $key . '="' . htmlspecialchars($this->config['blocked_src'], ENT_QUOTES) . '"'; + } + } + } + else if (preg_match('/^data:.+/i', $value)) { // RFC2397 + $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"'; + } + } + else { + $washed .= ($washed ? ' ' : '') . $key; + } + } + + return $t . ($washed && $this->config['show_washed'] ? ' x-washed="'.$washed.'"' : ''); + } + + /** + * The main loop that recurse on a node tree. + * It output only allowed tags with allowed attributes + * and allowed inline styles + */ + private function dumpHtml($node) + { + if (!$node->hasChildNodes()) { + return ''; + } + + $node = $node->firstChild; + $dump = ''; + + do { + switch($node->nodeType) { + case XML_ELEMENT_NODE: //Check element + $tagName = strtolower($node->tagName); + if ($callback = $this->handlers[$tagName]) { + $dump .= call_user_func($callback, $tagName, + $this->wash_attribs($node), $this->dumpHtml($node), $this); + } + else if (isset($this->_html_elements[$tagName])) { + $content = $this->dumpHtml($node); + $dump .= '<' . $tagName . $this->wash_attribs($node) . + ($content != '' || isset($this->_block_elements[$tagName]) ? ">$content" : ' />'); + } + else if (isset($this->_ignore_elements[$tagName])) { + $dump .= ''; + } + else { + $dump .= ''; + $dump .= $this->dumpHtml($node); // ignore tags not its content + } + break; + + case XML_CDATA_SECTION_NODE: + $dump .= $node->nodeValue; + break; + + case XML_TEXT_NODE: + $dump .= htmlspecialchars($node->nodeValue); + break; + + case XML_HTML_DOCUMENT_NODE: + $dump .= $this->dumpHtml($node); + break; + + case XML_DOCUMENT_TYPE_NODE: + break; + + default: + $dump . ''; + } + } while($node = $node->nextSibling); + + return $dump; + } + + /** + * Main function, give it untrusted HTML, tell it if you allow loading + * remote images and give it a map to convert "cid:" urls. + */ + public function wash($html) + { + // Charset seems to be ignored (probably if defined in the HTML document) + $node = new DOMDocument('1.0', $this->config['charset']); + $this->extlinks = false; + + $html = $this->cleanup($html); + + // Find base URL for images + if (preg_match('/config['base_url'] = $matches[1]; + } + else { + $this->config['base_url'] = ''; + } + + @$node->loadHTML($html); + return $this->dumpHtml($node); + } + + /** + * Getter for config parameters + */ + public function get_config($prop) + { + return $this->config[$prop]; + } + + /** + * Clean HTML input + */ + private function cleanup($html) + { + // special replacements (not properly handled by washtml class) + $html_search = array( + '/(<\/nobr>)(\s+)()/i', // space(s) between + '/]*>[^<]*<\/title>/i', // PHP bug #32547 workaround: remove title tag + '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/', // byte-order mark (only outlook?) + '/]+>/i', // washtml/DOMDocument cannot handle xml namespaces + ); + + $html_replace = array( + '\\1'.'   '.'\\3', + '', + '', + '', + ); + $html = preg_replace($html_search, $html_replace, trim($html)); + + // PCRE errors handling (#1486856), should we use something like for every preg_* use? + if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) { + $errstr = "Could not clean up HTML message! PCRE Error: $preg_error."; + + if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) { + $errstr .= " Consider raising pcre.backtrack_limit!"; + } + if ($preg_error == PREG_RECURSION_LIMIT_ERROR) { + $errstr .= " Consider raising pcre.recursion_limit!"; + } + + rcube::raise_error(array('code' => 620, 'type' => 'php', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => $errstr), true, false); + return ''; + } + + // fix (unknown/malformed) HTML tags before "wash" + $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', array($this, 'html_tag_callback'), $html); + + // Remove invalid HTML comments (#1487759) + // Don't remove valid conditional comments + $html = preg_replace('/'; - } - else { - $dump .= ''; - $dump .= $this->dumpHtml($node); // ignore tags not its content - } - break; - case XML_CDATA_SECTION_NODE: - $dump .= $node->nodeValue; - break; - case XML_TEXT_NODE: - $dump .= htmlspecialchars($node->nodeValue); - break; - case XML_HTML_DOCUMENT_NODE: - $dump .= $this->dumpHtml($node); - break; - case XML_DOCUMENT_TYPE_NODE: - break; - default: - $dump . ''; - } - } while($node = $node->nextSibling); - - return $dump; - } - - /* Main function, give it untrusted HTML, tell it if you allow loading - * remote images and give it a map to convert "cid:" urls. */ - public function wash($html) - { - // Charset seems to be ignored (probably if defined in the HTML document) - $node = new DOMDocument('1.0', $this->config['charset']); - $this->extlinks = false; - - // Find base URL for images - if (preg_match('/config['base_url'] = $matches[1]; - else - $this->config['base_url'] = ''; - - // Remove invalid HTML comments (#1487759) - // Don't remove valid conditional comments - $html = preg_replace('//', $html, "No external links allowed"); - $this->assertRegExp('/]+ target="_blank">/', $html, "Set target to _blank"); + $this->assertRegExp('/]+ target="_blank"/', $html, "Set target to _blank"); $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected"); // render HTML in safe mode @@ -133,8 +133,8 @@ class MailFunc extends PHPUnit_Framework_TestCase $html = rcmail_print_body($part, array('safe' => true)); $this->assertRegExp('/nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick"); - $this->assertRegExp('#http://www.apple.com/legal/privacy#', $html, "Links with target=_blank"); - $this->assertRegExp('#\\[http://example.com/\\?tx\\[a\\]=5\\]#', $html, "Links with square brackets"); + $this->assertRegExp('#http://www.apple.com/legal/privacy#', $html, "Links with target=_blank"); + $this->assertRegExp('#\\[http://example.com/\\?tx\\[a\\]=5\\]#', $html, "Links with square brackets"); } /** @@ -148,7 +148,7 @@ class MailFunc extends PHPUnit_Framework_TestCase $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo'); $mailto = 'e-mail'; + .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&body=this is the body\',this)" rel="noreferrer">e-mail'; $this->assertRegExp('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links"); } -- cgit v1.2.3 From 02c9c931fe34c699ded288b449c6d2d457a41a76 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 19 Mar 2013 13:53:49 +0100 Subject: Make mime.types common locations list OS-aware --- program/lib/Roundcube/rcube_mime.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index d21e3b4d5..ac4be95c0 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -718,21 +718,27 @@ class rcube_mime // load mapping file $file_paths = array(); - if ($mime_types = rcube::get_instance()->config->get('mime_types')) + if ($mime_types = rcube::get_instance()->config->get('mime_types')) { $file_paths[] = $mime_types; + } // try common locations - $file_paths[] = '/etc/mime.types'; - $file_paths[] = '/etc/httpd/mime.types'; - $file_paths[] = '/etc/httpd2/mime.types'; - $file_paths[] = '/etc/apache/mime.types'; - $file_paths[] = '/etc/apache2/mime.types'; - $file_paths[] = '/usr/local/etc/httpd/conf/mime.types'; - $file_paths[] = '/usr/local/etc/apache/conf/mime.types'; + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { + $file_paths[] = 'C:/xampp/apache/conf/mime.types.'; + } + else { + $file_paths[] = '/etc/mime.types'; + $file_paths[] = '/etc/httpd/mime.types'; + $file_paths[] = '/etc/httpd2/mime.types'; + $file_paths[] = '/etc/apache/mime.types'; + $file_paths[] = '/etc/apache2/mime.types'; + $file_paths[] = '/usr/local/etc/httpd/conf/mime.types'; + $file_paths[] = '/usr/local/etc/apache/conf/mime.types'; + } foreach ($file_paths as $fp) { if (is_readable($fp)) { - $lines = file($fp, FILE_IGNORE_NEW_LINES); + $lines = file($fp, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); break; } } -- cgit v1.2.3 From 4f693e9daa21185761d38dca9a0ba626be8c05cb Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 20 Mar 2013 10:31:37 +0100 Subject: Workaround for some versions/systems where finfo_open() with second argument doesn't do the same as with no 2nd argument as it should --- program/lib/Roundcube/rcube_mime.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index ac4be95c0..b70d681c9 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -672,7 +672,16 @@ class rcube_mime // try fileinfo extension if available if (!$mime_type && function_exists('finfo_open')) { - if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) { + // null as a 2nd argument should be the same as no argument + // this however is not true on all systems/versions + if ($mime_magic) { + $finfo = finfo_open(FILEINFO_MIME, $mime_magic); + } + else { + $finfo = finfo_open(FILEINFO_MIME); + } + + if ($finfo) { if ($is_stream) $mime_type = finfo_buffer($finfo, $path); else -- cgit v1.2.3 From 1bce1420589c62176dd34bb5fc0fab206a25d41e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 22 Mar 2013 09:35:47 +0100 Subject: Fix handling of some conditional comment tags in HTML message (#1489004) --- CHANGELOG | 1 + program/lib/Roundcube/rcube_washtml.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'program/lib') diff --git a/CHANGELOG b/CHANGELOG index ed0ea6ef3..541069004 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix handling of some conditional comment tags in HTML message (#1489004) - Add rel="noreferrer" for links in displayed messages (#1484686) - Fix so forward as attachment works if additional attachment is added by message_compose hook (#1489000) - Add ability to toggle between HTML and text while viewing a message (#1486939) diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index 2a261419f..27dff9f48 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -413,7 +413,8 @@ class rcube_washtml // Remove invalid HTML comments (#1487759) // Don't remove valid conditional comments - $html = preg_replace('/) conditional comments (#1489004) + $html = preg_replace('/