diff options
-rw-r--r-- | CHANGELOG | 7 | ||||
-rw-r--r-- | config/main.inc.php.dist | 6 | ||||
-rw-r--r-- | installer/config.php | 15 | ||||
-rw-r--r-- | program/include/html.php | 14 | ||||
-rw-r--r-- | program/lib/Mail/mime.php | 23 | ||||
-rw-r--r-- | program/lib/Mail/mimePart.php | 112 | ||||
-rw-r--r-- | program/localization/en_GB/labels.inc | 5 | ||||
-rw-r--r-- | program/localization/en_US/labels.inc | 5 | ||||
-rw-r--r-- | program/localization/pl_PL/labels.inc | 5 | ||||
-rw-r--r-- | program/steps/mail/sendmail.inc | 12 | ||||
-rw-r--r-- | program/steps/settings/func.inc | 12 | ||||
-rw-r--r-- | program/steps/settings/save_prefs.inc | 1 | ||||
-rw-r--r-- | skins/default/settings.css | 15 | ||||
-rw-r--r-- | skins/default/templates/settings.html | 18 |
14 files changed, 229 insertions, 21 deletions
@@ -1,6 +1,13 @@ CHANGELOG RoundCube Webmail --------------------------- +2008/09/29 (alec) +---------- +- 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 + 2008/09/25 (alec) ---------- - Fix problem with non-ascii attachment names in Mail_mime (#1485267, #1485096) diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 2ac713f58..19785cba6 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -377,5 +377,11 @@ $rcmail_config['logout_expunge'] = FALSE; // Display attached images below the message body $rcmail_config['inline_images'] = TRUE; +// Encoding of long/non-ascii attachment names: +// 0 - Full RFC 2231 compatible +// 1 - RFC 2047 for 'name' and RFC 2231 for 'filename' parameter (Thunderbird's default) +// 2 - Full 2047 compatible +$rcmail_config['mime_param_folding'] = 0; + // end of config file ?> diff --git a/installer/config.php b/installer/config.php index d5e10e8b4..b0f56889d 100644 --- a/installer/config.php +++ b/installer/config.php @@ -539,6 +539,21 @@ echo $select_mdnreq->show(intval($RCI->getprop('mdn_requests'))); <div>Behavior if a received message requests a message delivery notification (read receipt)</div> </dd> +<dt class="propname">mime_param_folding <span class="userconf">*</span></dt> +<dd> +<?php + +$select_param_folding = new html_select(array('name' => '_mime_param_folding', 'id' => "cfgmimeparamfolding")); +$select_param_folding->add('Full RFC 2231 (Roundcube, Thunderbird)', '0'); +$select_param_folding->add('RFC 2047/2231 (MS Outlook, OE)', '1'); +$select_param_folding->add('Full RFC 2047 (deprecated)', '2'); + +echo $select_param_folding->show(intval($RCI->getprop('mime_param_folding'))); + +?> +<div>How to encode attachment long/non-ascii names</div> +</dd> + </dl> <p class="hint"><span class="userconf">*</span> These settings are defaults for the user preferences</p> 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), diff --git a/skins/default/settings.css b/skins/default/settings.css index 8c229a15c..9621a21a2 100644 --- a/skins/default/settings.css +++ b/skins/default/settings.css @@ -63,6 +63,11 @@ span.tablink-selected a padding-right: 10px; } +#userprefs-box table tr.advanced +{ + display: none; +} + #identities-list, #folder-manager { @@ -253,3 +258,13 @@ legend { color: #999999; } + +div.advswitch +{ + white-space: nowrap; + text-align: right; + position: absolute; + bottom: 35px; + left: 20px; + width: 640px; +} diff --git a/skins/default/templates/settings.html b/skins/default/templates/settings.html index 2b69f423d..0bc0f1853 100644 --- a/skins/default/templates/settings.html +++ b/skins/default/templates/settings.html @@ -4,6 +4,15 @@ <title><roundcube:object name="pagetitle" /></title> <roundcube:include file="/includes/links.html" /> <link rel="stylesheet" type="text/css" href="/settings.css" /> +<script type="text/javascript"> +function show_adv(box) +{ + var rows = document.getElementsByTagName('TR'); + for(var i=0; i<rows.length; i++) + if(rows[i].className && rows[i].className.match(/advanced/)) + rows[i].style.display = box.checked ? (bw.ie ? 'block' : 'table-row') : 'none'; +} +</script> </head> <body> @@ -19,7 +28,14 @@ </div> </div> -<p id="listbuttons"><roundcube:button command="save" type="input" class="button mainaction" label="save" /></p> +<p id="listbuttons"> +<roundcube:button command="save" type="input" class="button mainaction" label="save" /> +</p> + +<div class="advswitch"> +<label for="advswitch"><roundcube:label name="advancedoptions"><label> +<input type="checkbox" id="advswitch" name="_advanced" value="0" onclick="show_adv(this)" /> +</div> <roundcube:include file="/includes/settingscripts.html" /> |