diff --git a/CHANGELOG b/CHANGELOG index cbffcee50..7556cfe56 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG RoundCube Webmail =========================== +- Support UTF-7 encoding in messages (#1485832) - Better support for malformed character names (#1485758) - Added possibility to encrypt received header, option 'http_received_header_encrypt', added some more logic in encrypt/decrypt functions for security diff --git a/program/include/main.inc b/program/include/main.inc index f111ac9a6..7a0f0ca16 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -205,7 +205,6 @@ function rcube_charset_convert($str, $from, $to=NULL) // convert charset using mbstring module if ($mbstring_loaded) { - $aliases['UTF-7'] = 'UTF7-IMAP'; $aliases['WINDOWS-1257'] = 'ISO-8859-13'; if (is_null($mbstring_list)) { @@ -220,9 +219,6 @@ function rcube_charset_convert($str, $from, $to=NULL) if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) { if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from))) return $out; - else - // return here, encoding supported, but string is invalid - return $str; } } @@ -231,35 +227,49 @@ function rcube_charset_convert($str, $from, $to=NULL) $conv = new utf8(); // convert string to UTF-8 - if ($from == 'UTF-7') { - if ($_str = utf7_to_utf8($str)) - $str = $_str; - else + if ($to == 'UTF-8') { + if ($from == 'UTF7-IMAP') { + if ($_str = utf7_to_utf8($str)) + $str = $_str; + else + $error = true; + } + else if ($from == 'UTF-7') { + if ($_str = rcube_utf7_to_utf8($str)) + $str = $_str; + else + $error = true; + } + else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) { + $str = utf8_encode($str); + } + else if ($from != 'UTF-8' && $conv) { + $conv->loadCharset($from); + $str = $conv->strToUtf8($str); + } + else if ($from != 'UTF-8') $error = true; } - else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) { - $str = utf8_encode($str); - } - else if ($from != 'UTF-8' && $conv) { - $conv->loadCharset($from); - $str = $conv->strToUtf8($str); - } - else if ($from != 'UTF-8') - $error = true; - + // encode string for output - if ($to == 'UTF-7') { - return utf8_to_utf7($str); - } - else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) { - return utf8_decode($str); - } - else if ($to != 'UTF-8' && $conv) { - $conv->loadCharset($to); - return $conv->utf8ToStr($str); - } - else if ($to != 'UTF-8') { - $error = true; + if ($from == 'UTF-8') { + // @TODO: we need a function for UTF-7 (RFC2152) conversion + if ($to == 'UTF7-IMAP' || $to == 'UTF-7') { + if ($_str = utf8_to_utf7($str)) + $str = $_str; + else + $error = true; + } + else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) { + return utf8_decode($str); + } + else if ($to != 'UTF-8' && $conv) { + $conv->loadCharset($to); + return $conv->utf8ToStr($str); + } + else if ($to != 'UTF-8') { + $error = true; + } } // report error @@ -304,6 +314,7 @@ function rcube_parse_charset($charset) 'ISO88598I' => 'ISO-8859-8', 'KSC56011987' => 'EUC-KR', 'UNICODE' => 'UTF-8', + 'UTF7IMAP' => 'UTF7-IMAP' ); $str = preg_replace('/[^a-z0-9]/i', '', $charset); @@ -321,6 +332,93 @@ function rcube_parse_charset($charset) } +/** + * Converts string from standard UTF-7 (RFC 2152) to UTF-8. + * + * @param string Input string + * @return The converted string + */ +function rcube_utf7_to_utf8($str) +{ + $Index_64 = array( + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0, + 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0, + 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, + 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0, + 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, + 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0, + ); + + $u7len = strlen($str); + $str = strval($str); + $res = ''; + + for ($i=0; $u7len > 0; $i++, $u7len--) + { + $u7 = $str[$i]; + if ($u7 == '+') + { + $i++; + $u7len--; + $ch = ''; + + for (; $u7len > 0; $i++, $u7len--) + { + $u7 = $str[$i]; + + if (!$Index_64[ord($u7)]) + break; + + $ch .= $u7; + } + + if ($ch == '') { + if ($u7 == '-') + $res .= '+'; + continue; + } + + $res .= rcube_utf16_to_utf8(base64_decode($ch)); + } + else + { + $res .= $u7; + } + } + + return $res; +} + +/** + * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion) + * + * @param string Input string + * @return The converted string + */ +function rcube_utf16_to_utf8($str) +{ + $len = strlen($str); + $dec = ''; + + for ($i = 0; $i < $len; $i += 2) { + $c = ord($str[$i]) << 8 | ord($str[$i + 1]); + if ($c >= 0x0001 && $c <= 0x007F) { + $dec .= chr($c); + } else if ($c > 0x07FF) { + $dec .= chr(0xE0 | (($c >> 12) & 0x0F)); + $dec .= chr(0x80 | (($c >> 6) & 0x3F)); + $dec .= chr(0x80 | (($c >> 0) & 0x3F)); + } else { + $dec .= chr(0xC0 | (($c >> 6) & 0x1F)); + $dec .= chr(0x80 | (($c >> 0) & 0x3F)); + } + } + return $dec; +} + + /** * Replacing specials characters to a specific encoding type * @@ -408,7 +506,7 @@ function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE) if ($enctype=='js') { if ($charset!='UTF-8') - $str = rcube_charset_convert($str, RCMAIL_CHARSET,$charset); + $str = rcube_charset_convert($str, RCMAIL_CHARSET, $charset); return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table)); } @@ -1057,7 +1155,7 @@ function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='') if (!isset($arrFolders[$currentFolder])) { $arrFolders[$currentFolder] = array( 'id' => $path, - 'name' => rcube_charset_convert($currentFolder, 'UTF-7'), + 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'), 'virtual' => $virtual, 'folders' => array()); } @@ -1232,7 +1330,7 @@ function rcmail_localize_foldername($name) if ($folder_class = rcmail_folder_classname($name)) return rcube_label($folder_class); else - return rcube_charset_convert($name, 'UTF-7'); + return rcube_charset_convert($name, 'UTF7-IMAP'); } diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php index 60064e7f5..56f577bda 100644 --- a/program/include/rcube_config.php +++ b/program/include/rcube_config.php @@ -78,11 +78,11 @@ class rcube_config // fix default imap folders encoding foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder) - $this->prop[$folder] = rcube_charset_convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF-7'); + $this->prop[$folder] = rcube_charset_convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF7-IMAP'); if (!empty($this->prop['default_imap_folders'])) foreach ($this->prop['default_imap_folders'] as $n => $folder) - $this->prop['default_imap_folders'][$n] = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF-7'); + $this->prop['default_imap_folders'][$n] = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP'); // set PHP error logging according to config if ($this->prop['debug_level'] & 1) { diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 67be40346..ca3551cb2 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -2762,7 +2762,7 @@ class rcube_imap if (($p = array_search(strtolower($folder), $this->default_folders_lc)) !== false && !$a_defaults[$p]) $a_defaults[$p] = $folder; else - $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7')); + $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF7-IMAP')); } // sort folders and place defaults on the top diff --git a/program/steps/settings/manage_folders.inc b/program/steps/settings/manage_folders.inc index 79d313c37..22bd158f5 100644 --- a/program/steps/settings/manage_folders.inc +++ b/program/steps/settings/manage_folders.inc @@ -19,7 +19,7 @@ */ -// WARNING: folder names in UI are encoded with UTF-8 +// WARNING: folder names in UI are encoded with RCMAIL_CHARSET // init IMAP connection $RCMAIL->imap_init(true); @@ -27,14 +27,14 @@ $RCMAIL->imap_init(true); // subscribe to one or more mailboxes if ($RCMAIL->action=='subscribe') { - if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF-7')) + if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF7-IMAP')) $IMAP->subscribe(array($mbox)); } // unsubscribe one or more mailboxes else if ($RCMAIL->action=='unsubscribe') { - if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF-7')) + if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF7-IMAP')) $IMAP->unsubscribe(array($mbox)); } @@ -43,7 +43,7 @@ else if ($RCMAIL->action=='create-folder') { if (!empty($_POST['_name'])) { - $name = trim(get_input_value('_name', RCUBE_INPUT_POST, FALSE, 'UTF-7')); + $name = trim(get_input_value('_name', RCUBE_INPUT_POST, FALSE, 'UTF7-IMAP')); $create = $IMAP->create_mailbox($name, TRUE); } @@ -52,9 +52,9 @@ else if ($RCMAIL->action=='create-folder') $delimiter = $IMAP->get_hierarchy_delimiter(); $folderlist = $IMAP->list_unsubscribed(); $index = array_search($create, $folderlist); - $before = $index !== false && isset($folderlist[$index+1]) ? rcube_charset_convert($folderlist[$index+1], 'UTF-7') : false; + $before = $index !== false && isset($folderlist[$index+1]) ? rcube_charset_convert($folderlist[$index+1], 'UTF7-IMAP') : false; - $create = rcube_charset_convert($create, 'UTF-7'); + $create = rcube_charset_convert($create, 'UTF7-IMAP'); $foldersplit = explode($delimiter, $create); $display_create = str_repeat('    ', substr_count($create, $delimiter)) . $foldersplit[count($foldersplit)-1]; @@ -73,8 +73,8 @@ else if ($RCMAIL->action=='rename-folder') { $name_utf8 = trim(get_input_value('_folder_newname', RCUBE_INPUT_POST)); $oldname_utf8 = get_input_value('_folder_oldname', RCUBE_INPUT_POST); - $name = rcube_charset_convert($name_utf8, 'UTF-8', 'UTF-7'); - $oldname = rcube_charset_convert($oldname_utf8, 'UTF-8', 'UTF-7'); + $name = rcube_charset_convert($name_utf8, RCMAIL_CHARSET, 'UTF7-IMAP'); + $oldname = rcube_charset_convert($oldname_utf8, RCMAIL_CHARSET, 'UTF7-IMAP'); $rename = $IMAP->rename_mailbox($oldname, $name); } @@ -95,23 +95,22 @@ else if ($RCMAIL->action=='rename-folder') $foldersplit = explode($delimiter, $folderlist[$x]); $level = count($foldersplit) - 1; $display_rename = str_repeat('    ', $level) - . rcube_charset_convert($foldersplit[$level], 'UTF-7'); + . rcube_charset_convert($foldersplit[$level], 'UTF7-IMAP'); - $before = isset($folderlist[$x+1]) ? rcube_charset_convert($folderlist[$x+1], 'UTF-7') : false; + $before = isset($folderlist[$x+1]) ? rcube_charset_convert($folderlist[$x+1], 'UTF7-IMAP') : false; - $OUTPUT->command('replace_folder_row', rcube_charset_convert($oldfolder, 'UTF-7'), - rcube_charset_convert($folderlist[$x], 'UTF-7'), $display_rename, $before); + $OUTPUT->command('replace_folder_row', rcube_charset_convert($oldfolder, 'UTF7-IMAP'), + rcube_charset_convert($folderlist[$x], 'UTF7-IMAP'), $display_rename, $before); } } $foldersplit = explode($delimiter, $rename); $level = count($foldersplit) - 1; - $display_rename = str_repeat('    ', $level) . rcube_charset_convert($foldersplit[$level], 'UTF-7'); + $display_rename = str_repeat('    ', $level) . rcube_charset_convert($foldersplit[$level], 'UTF7-IMAP'); $index = array_search($rename, $folderlist); - $before = $index !== false && isset($folderlist[$index+1]) ? rcube_charset_convert($folderlist[$index+1], 'UTF-7') : false; - - $OUTPUT->command('replace_folder_row', $oldname_utf8, rcube_charset_convert($rename, 'UTF-7'), $display_rename, $before); + $before = $index !== false && isset($folderlist[$index+1]) ? rcube_charset_convert($folderlist[$index+1], 'UTF7-IMAP') : false; + $OUTPUT->command('replace_folder_row', $oldname_utf8, rcube_charset_convert($rename, 'UTF7-IMAP'), $display_rename, $before); $OUTPUT->command('reset_folder_rename'); } else if (!$rename && $OUTPUT->ajax_call) @@ -130,7 +129,7 @@ else if ($RCMAIL->action=='delete-folder') $delimiter = $IMAP->get_hierarchy_delimiter(); $mboxes_utf8 = get_input_value('_mboxes', RCUBE_INPUT_POST); - $mboxes = rcube_charset_convert($mboxes_utf8, 'UTF-8', 'UTF-7'); + $mboxes = rcube_charset_convert($mboxes_utf8, RCMAIL_CHARSET, 'UTF7-IMAP'); if ($mboxes) $deleted = $IMAP->delete_mailbox(array($mboxes)); @@ -142,7 +141,7 @@ else if ($RCMAIL->action=='delete-folder') { if (preg_match('/^'. preg_quote($mboxes.$delimiter, '/') .'/', $mbox)) { - $OUTPUT->command('remove_folder_row', rcube_charset_convert($mbox, 'UTF-7')); + $OUTPUT->command('remove_folder_row', rcube_charset_convert($mbox, 'UTF7-IMAP')); } } $OUTPUT->show_message('folderdeleted', 'confirmation'); @@ -189,7 +188,7 @@ function rcube_subscription_form($attrib) // pre-process folders list foreach ($a_unsubscribed as $i => $folder) { $foldersplit = explode($delimiter, $folder); - $name = rcube_charset_convert(array_pop($foldersplit), 'UTF-7'); + $name = rcube_charset_convert(array_pop($foldersplit), 'UTF7-IMAP'); $parent_folder = join($delimiter, $foldersplit); $level = count($foldersplit); @@ -198,7 +197,7 @@ function rcube_subscription_form($attrib) for ($i=1; $i<=$level; $i++) { $ancestor_folder = join($delimiter, array_slice($foldersplit, 0, $i)); if ($ancestor_folder && !$seen[$ancestor_folder]++) { - $ancestor_name = rcube_charset_convert($foldersplit[$i-1], 'UTF-7'); + $ancestor_name = rcube_charset_convert($foldersplit[$i-1], 'UTF7-IMAP'); $list_folders[] = array('id' => $ancestor_folder, 'name' => $ancestor_name, 'level' => $i-1, 'virtual' => true); } } @@ -231,7 +230,7 @@ function rcube_subscription_form($attrib) $classes = array($i%2 ? 'even' : 'odd'); $folder_js = JQ($folder['id']); $display_folder = str_repeat('    ', $folder['level']) . ($protected ? rcmail_localize_foldername($folder['id']) : $folder['name']); - $folder_utf8 = rcube_charset_convert($folder['id'], 'UTF-7'); + $folder_utf8 = rcube_charset_convert($folder['id'], 'UTF7-IMAP'); if ($folder['virtual']) $classes[] = 'virtual';