From ffae15e5fcde50cf8c1b168fa313f4ec3454a693 Mon Sep 17 00:00:00 2001 From: alecpl Date: Mon, 29 Sep 2008 07:38:16 +0000 Subject: - Added 'mime_param_folding' option with possibility to choose long/non-ascii attachment names encoding eg. to be readable in MS Outlook/OE (#1485320) - Added "advanced options" feature in User Preferences --- program/include/html.php | 14 ++++- program/lib/Mail/mime.php | 23 ++++++- program/lib/Mail/mimePart.php | 112 ++++++++++++++++++++++++++++++---- program/localization/en_GB/labels.inc | 5 ++ program/localization/en_US/labels.inc | 5 ++ program/localization/pl_PL/labels.inc | 5 ++ program/steps/mail/sendmail.inc | 12 +++- program/steps/settings/func.inc | 12 ++++ program/steps/settings/save_prefs.inc | 1 + 9 files changed, 169 insertions(+), 20 deletions(-) (limited to 'program') diff --git a/program/include/html.php b/program/include/html.php index 2d0d90b5f..9268acacc 100644 --- a/program/include/html.php +++ b/program/include/html.php @@ -591,7 +591,7 @@ class html_table extends html public function add_header($attr, $cont) { if (is_string($attr)) - $attr = array('class' => $attr); + $attr = array('class' => $attr); $cell = new stdClass; $cell->attrib = $attr; @@ -613,6 +613,18 @@ class html_table extends html $this->rows[$this->rowindex]->cells = array(); } + /** + * Set current row attrib + * + * @param array Row attributes + */ + public function set_row_attribs($attr = array()) + { + if (is_string($attr)) + $attr = array('class' => $attr); + + $this->rows[$this->rowindex]->attrib = $attr; + } /** * Build HTML output of the table data diff --git a/program/lib/Mail/mime.php b/program/lib/Mail/mime.php index 7641b0c9b..688c326a5 100644 --- a/program/lib/Mail/mime.php +++ b/program/lib/Mail/mime.php @@ -323,6 +323,8 @@ class Mail_mime * of this attachment. * @param string $language The language of the attachment * @param string $location The RFC 2557.4 location of the attachment + * @param string $n_encoding Use RFC 2047 for attachment name (Content-Type) encoding + * @param string $f_encoding Use RFC 2047 for attachment filename (Content-Disposition) encoding * * @return mixed true on success or PEAR_Error object * @access public @@ -335,7 +337,9 @@ class Mail_mime $disposition = 'attachment', $charset = '', $language = '', - $location = '') + $location = '', + $n_encoding = NULL, + $f_encoding = NULL) { $filedata = ($isfile === true) ? $this->_file2str($file) : $file; @@ -363,7 +367,9 @@ class Mail_mime 'charset' => $charset, 'language' => $language, 'location' => $location, - 'disposition' => $disposition + 'disposition' => $disposition, + 'name-encoding' => $n_encoding, + 'filename-encoding'=> $f_encoding ); return true; } @@ -532,6 +538,12 @@ class Mail_mime $params['disposition'] = 'inline'; $params['dfilename'] = $value['name']; $params['cid'] = $value['cid']; + if ($value['name-encoding']) { + $params['name-encoding'] = $value['name-encoding']; + } + if ($value['filename-encoding']) { + $params['filename-encoding'] = $value['filename-encoding']; + } $ret = $obj->addSubpart($value['body'], $params); return $ret; @@ -561,6 +573,12 @@ class Mail_mime if ($value['location']) { $params['location'] = $value['location']; } + if ($value['name-encoding']) { + $params['name-encoding'] = $value['name-encoding']; + } + if ($value['filename-encoding']) { + $params['filename-encoding'] = $value['filename-encoding']; + } $params['content_type'] = $value['c_type']; $params['disposition'] = isset($value['disposition']) ? $value['disposition'] : 'attachment'; @@ -914,7 +932,6 @@ class Mail_mime */ function _encodeHeaders($input, $params = array()) { - $build_params = $this->_build_params; while (list($key, $value) = each($params)) { $build_params[$key] = $value; diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php index bd3047724..9b9f26cc3 100644 --- a/program/lib/Mail/mimePart.php +++ b/program/lib/Mail/mimePart.php @@ -182,13 +182,16 @@ class Mail_mimePart { } } + if (isset($contentType['type'])) { $headers['Content-Type'] = $contentType['type']; if (isset($contentType['name'])) { $headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF; - $headers['Content-Type'] .= $this->_buildHeaderParam('name', $contentType['name'], - isset($contentType['charset']) ? $contentType['charset'] : 'US-ASCII', - isset($contentType['language']) ? $contentType['language'] : NULL); + $headers['Content-Type'] .= + $this->_buildHeaderParam('name', $contentType['name'], + isset($contentType['charset']) ? $contentType['charset'] : 'US-ASCII', + isset($contentType['language']) ? $contentType['language'] : NULL, + isset($params['name-encoding']) ? $params['name-encoding'] : NULL); } elseif (isset($contentType['charset'])) { $headers['Content-Type'] .= "; charset=\"{$contentType['charset']}\""; } @@ -199,15 +202,14 @@ class Mail_mimePart { $headers['Content-Disposition'] = $contentDisp['disp']; if (isset($contentDisp['filename'])) { $headers['Content-Disposition'] .= ';' . MAIL_MIMEPART_CRLF; - $headers['Content-Disposition'] .= $this->_buildHeaderParam('filename', $contentDisp['filename'], - isset($contentDisp['charset']) ? $contentDisp['charset'] : 'US-ASCII', - isset($contentDisp['language']) ? $contentDisp['language'] : NULL); + $headers['Content-Disposition'] .= + $this->_buildHeaderParam('filename', $contentDisp['filename'], + isset($contentDisp['charset']) ? $contentDisp['charset'] : 'US-ASCII', + isset($contentDisp['language']) ? $contentDisp['language'] : NULL, + isset($params['filename-encoding']) ? $params['filename-encoding'] : NULL); } } - - - - + // Default content-type if (!isset($headers['Content-Type'])) { $headers['Content-Type'] = 'text/plain'; @@ -388,21 +390,25 @@ class Mail_mimePart { * @param $value The value of the paramter * @param $charset The characterset of $value * @param $language The language used in $value + * @param $paramEnc Parameter encoding type * @param $maxLength The maximum length of a line. Defauls to 78 * * @access private */ - function _buildHeaderParam($name, $value, $charset=NULL, $language=NULL, $maxLength=78) + function _buildHeaderParam($name, $value, $charset=NULL, $language=NULL, $paramEnc=NULL, $maxLength=78) { // RFC 2183/2184/2822: // value needs encoding if contains non-ASCII chars or is longer than 78 chars - if (!preg_match('#[^\x20-\x7E]#', $value)) { // ASCII $quoted = addcslashes($value, '\\"'); if (strlen($name) + strlen($quoted) + 6 <= $maxLength) return " {$name}=\"{$quoted}\"; "; } + // use quoted-printable/base64 encoding (RFC2047) + if ($paramEnc == 'quoted-printable' || $paramEnc == 'base64') + return $this->_buildRFC2047Param($name, $value, $charset, $paramEnc); + $encValue = preg_replace('#([^\x20-\x7E])#e', '"%" . strtoupper(dechex(ord("\1")))', $value); $value = "$charset'$language'$encValue"; @@ -413,7 +419,7 @@ class Mail_mimePart { $preLength = strlen(" {$name}*0*=\""); $sufLength = strlen("\";"); - $maxLength = MAX(16, $maxLength - $preLength - $sufLength - 2); + $maxLength = max(16, $maxLength - $preLength - $sufLength - 2); $maxLengthReg = "|(.{0,$maxLength}[^\%][^\%])|"; $headers = array(); @@ -433,4 +439,84 @@ class Mail_mimePart { $headers = implode(MAIL_MIMEPART_CRLF, $headers) . ';'; return $headers; } + + /** + * Encodes header parameter as per RFC2047 if needed (values too long will be truncated) + * + * @param string $name The parameter name + * @param string $value The parameter value + * @param string $charset The parameter charset + * @param string $encoding Encoding type (quoted-printable or base64) + * @param int $maxLength Encoded parameter max length (75 is the value specified in the RFC) + * + * @return string Parameter line + * @access private + */ + function _buildRFC2047Param($name, $value, $charset, $encoding='quoted-printable', $maxLength=75) + { + if (!preg_match('#([^\x20-\x7E]){1}#', $value)) + { + $quoted = addcslashes($value, '\\"'); + $maxLength = $maxLength - 6; + if (strlen($quoted) > $maxLength) + { + // truncate filename leaving extension + $ext = strrchr($quoted, '.'); + $quoted = substr($quoted, 0, $maxLength - strlen($ext)); + // remove backslashes from the end of filename + preg_replace('/[\\\\]+$/', '', $quoted); + $quoted .= $ext; + } + } + else if ($encoding == 'base64') + { + $ext = strrchr($value, '.'); + $value = substr($value, 0, strlen($value) - strlen($ext)); + + $ext = base64_encode($ext); + $value = base64_encode($value); + + $prefix = '=?' . $charset . '?B?'; + $suffix = '?='; + $maxLength = $maxLength - strlen($prefix . $suffix) - strlen($ext) - 2; + + //We can cut base64 every 4 characters, so the real max + //we can get must be rounded down. + $maxLength = $maxLength - ($maxLength % 4); + $quoted = $prefix . substr($value, 0, $maxLength) . $ext . $suffix; + } + else // quoted-printable + { + $ext = strrchr($value, '.'); + $value = substr($value, 0, strlen($value) - strlen($ext)); + + // Replace all special characters used by the encoder. + $search = array('=', '_', '?', ' '); + $replace = array('=3D', '=5F', '=3F', '_'); + $ext = str_replace($search, $replace, $ext); + $value = str_replace($search, $replace, $value); + + // Replace all extended characters (\x80-xFF) with their + // ASCII values. + $ext = preg_replace('/([\x80-\xFF])/e', + '"=" . strtoupper(dechex(ord("\1")))', $ext); + $value = preg_replace('/([\x80-\xFF])/e', + '"=" . strtoupper(dechex(ord("\1")))', $value); + + $prefix = '=?' . $charset . '?Q?'; + $suffix = '?='; + + $maxLength = $maxLength - strlen($prefix . $suffix) - strlen($ext) - 2; + + // Truncate QP-encoded text at $maxLength + // but not break any encoded letters. + if(preg_match("/^(.{0,$maxLength}[^\=][^\=])/", $value, $matches)) + $value = $matches[1]; + + $quoted = $prefix . $value . $ext . $suffix; + } + + return " {$name}=\"{$quoted}\"; "; + } + } // End of class diff --git a/program/localization/en_GB/labels.inc b/program/localization/en_GB/labels.inc index 13008f535..064a6881c 100644 --- a/program/localization/en_GB/labels.inc +++ b/program/localization/en_GB/labels.inc @@ -227,6 +227,11 @@ $labels['showinlineimages'] = 'Display attached images below the message'; $labels['autosavedraft'] = 'Automatically save draft'; $labels['everynminutes'] = 'every $n minutes'; $labels['never'] = 'never'; +$labels['mimeparamfolding'] = 'Attachment names'; +$labels['2231folding'] = 'Full RFC 2231 (Thunderbird)'; +$labels['miscfolding'] = 'RFC 2047/2231 (MS Outlook)'; +$labels['2047folding'] = 'Full RFC 2047 (other)'; +$labels['advancedoptions'] = 'advanced options'; $labels['messagesdisplaying'] = 'Displaying messages'; $labels['messagescomposition'] = 'Composing messages'; $labels['folder'] = 'Folder'; diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 380352e79..28aab267d 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -284,6 +284,11 @@ $labels['everynminutes'] = 'every $n minutes'; $labels['never'] = 'never'; $labels['messagesdisplaying'] = 'Displaying messages'; $labels['messagescomposition'] = 'Composing messages'; +$labels['mimeparamfolding'] = 'Attachment names'; +$labels['2231folding'] = 'Full RFC 2231 (Thunderbird)'; +$labels['miscfolding'] = 'RFC 2047/2231 (MS Outlook)'; +$labels['2047folding'] = 'Full RFC 2047 (other)'; +$labels['advancedoptions'] = 'advanced options'; $labels['folder'] = 'Folder'; $labels['folders'] = 'Folders'; diff --git a/program/localization/pl_PL/labels.inc b/program/localization/pl_PL/labels.inc index b35deeeee..de87be38d 100644 --- a/program/localization/pl_PL/labels.inc +++ b/program/localization/pl_PL/labels.inc @@ -220,6 +220,11 @@ $labels['mdnrequests'] = 'Potwierdzenia odbioru'; $labels['askuser'] = 'spytaj użytkownika'; $labels['autosend'] = 'wyślij automatycznie'; $labels['ignore'] = 'ignoruj'; +$labels['mimeparamfolding'] = 'Nazwy załączników'; +$labels['2231folding'] = 'zgodne z RFC 2231 (Thunderbird)'; +$labels['miscfolding'] = 'zgodne z RFC 2047/2231 (MS Outlook)'; +$labels['2047folding'] = 'zgodne z RFC 2047 (inne)'; +$labels['advancedoptions'] = 'opcje zaawansowane'; $labels['readwhendeleted'] = 'Podczas usuwania oznacz wiadomość jako przeczytaną'; $labels['flagfordeletion'] = 'Oznacz wiadomość do usunięcia zamiast ją usuwać'; $labels['autosavedraft'] = 'Automatyczny zapis tworzonej wiadomości'; diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 543895dec..d342124a4 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -304,7 +304,10 @@ if (is_array($_SESSION['compose']['attachments'])) $attachment['name'], true, ($ctype == 'message/rfc822' ? $transfer_encoding : 'base64'), ($ctype == 'message/rfc822' ? 'inline' : 'attachment'), - $message_charset); + $message_charset, '', '', + $CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL, + $CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL + ); } } @@ -316,8 +319,11 @@ if (is_array($_FILES['_attachments']['tmp_name'])) $ctype = str_replace('image/pjpeg', 'image/jpeg', $ctype); // #1484914 $MAIL_MIME->addAttachment($filepath, $ctype, $files['name'][$i], true, - ($ctype == 'message/rfc822' ? $transfer_encoding : 'base64'), - 'attachment', $message_charset); + $ctype == 'message/rfc822' ? $transfer_encoding : 'base64', + 'attachment', $message_charset, '', '', + $CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL, + $CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL + ); } diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index f115ad900..04079e16f 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -224,6 +224,18 @@ function rcmail_user_prefs_form($attrib) $table->add(null, $select_autosave->show($config['draft_autosave'])); } + if (!isset($no_override['mime_param_folding'])) { + $field_id = 'rcmfd_param_folding'; + $select_param_folding = new html_select(array('name' => '_mime_param_folding', 'id' => $field_id)); + $select_param_folding->add(rcube_label('2231folding'), 0); + $select_param_folding->add(rcube_label('miscfolding'), 1); + $select_param_folding->add(rcube_label('2047folding'), 2); + + $table->set_row_attribs('advanced'); + $table->add('title', html::label($field_id, Q(rcube_label('mimeparamfolding')))); + $table->add(null, $select_param_folding->show($config['mime_param_folding'])); + } + $out .= html::tag('fieldset', null, html::tag('legend', null, Q(rcube_label('messagescomposition'))) . $table->show($attrib)); // Configure special folders diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc index 8e2a27953..b3b75ffe4 100644 --- a/program/steps/settings/save_prefs.inc +++ b/program/steps/settings/save_prefs.inc @@ -35,6 +35,7 @@ $a_user_prefs = array( 'logout_purge' => isset($_POST['_logout_purge']) ? TRUE : FALSE, 'logout_expunge' => isset($_POST['_logout_expunge']) ? TRUE : FALSE, 'draft_autosave' => isset($_POST['_draft_autosave']) ? intval($_POST['_draft_autosave']) : 0, + 'mime_param_folding' => isset($_POST['_mime_param_folding']) ? intval($_POST['_mime_param_folding']) : 0, 'mdn_requests' => isset($_POST['_mdn_requests']) ? intval($_POST['_mdn_requests']) : 0, 'skin' => isset($_POST['_skin']) ? get_input_value('_skin', RCUBE_INPUT_POST) : $CONFIG['skin'], 'drafts_mbox' => get_input_value('_drafts_mbox', RCUBE_INPUT_POST), -- cgit v1.2.3