From 9287ed36b368f2c41d02293b781bb061a6875eef Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 11 Sep 2012 09:15:24 +0200 Subject: - Replace data URIs of images (pasted in HTML editor) with inline attachments (#1488502) --- program/steps/mail/sendmail.inc | 55 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) (limited to 'program/steps') diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 577751742..5c2c6de20 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -93,9 +93,8 @@ function rcmail_get_identity($id) * to this: * * Cool - * ... */ -function rcmail_fix_emoticon_paths(&$mime_message) +function rcmail_fix_emoticon_paths($mime_message) { global $CONFIG; @@ -134,8 +133,53 @@ function rcmail_fix_emoticon_paths(&$mime_message) } $mime_message->setHTMLBody($body); +} + +/** + * Extract image attachments from HTML content (data URIs) + */ +function rcmail_extract_inline_images($mime_message, $from) +{ + $body = $mime_message->getHTMLBody(); + $offset = 0; + $list = array(); + $regexp = '# src=[\'"](data:(image/[a-z]+);base64,([a-z0-9+/=\r\n]+))([\'"])#i'; + + // get domain for the Content-ID, must be the same as in Mail_Mime::get() + if (preg_match('#@([0-9a-zA-Z\-\.]+)#', $from, $matches)) { + $domain = $matches[1]; + } else { + $domain = 'localhost'; + } + + if (preg_match_all($regexp, $body, $matches, PREG_OFFSET_CAPTURE)) { + foreach ($matches[1] as $idx => $m) { + $data = preg_replace('/\r\n/', '', $matches[3][$idx][0]); + $data = base64_decode($data); - return $body; + if (empty($data)) { + continue; + } + + $hash = md5($data) . '@' . $domain; + $mime_type = $matches[2][$idx][0]; + $name = $list[$hash]; + + // add the image to the MIME message + if (!$name) { + $ext = preg_replace('#^[^/]+/#', '', $mime_type); + $name = substr($hash, 0, 8) . '.' . $ext; + $list[$hash] = $name; + + $mime_message->addHTMLImage($data, $mime_type, $name, false, $hash); + } + + $body = substr_replace($body, $name, $m[1] + $offset, strlen($m[0])); + $offset += strlen($name) - strlen($m[0]); + } + } + + $mime_message->setHTMLBody($body); } /** @@ -522,7 +566,10 @@ if ($isHtml) { // look for "emoticon" images from TinyMCE and change their src paths to // be file paths on the server instead of URL paths. - $message_body = rcmail_fix_emoticon_paths($MAIL_MIME); + rcmail_fix_emoticon_paths($MAIL_MIME); + + // Extract image Data URIs into message attachments (#1488502) + rcmail_extract_inline_images($MAIL_MIME, $from); } else { $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body', -- cgit v1.2.3 From 1e9aa256091f56589e75489deff3259a3586ad1f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 11 Sep 2012 19:34:35 +0200 Subject: Fix error where session wasn't updated after folder rename/delete (#1488692) --- CHANGELOG | 1 + program/steps/settings/folders.inc | 14 ++++++++++++-- program/steps/settings/save_folder.inc | 11 ++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) (limited to 'program/steps') diff --git a/CHANGELOG b/CHANGELOG index f3960e686..4f5ace427 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix error where session wasn't updated after folder rename/delete (#1488692) - Replace data URIs of images (pasted in HTML editor) with inline attachments (#1488502) - Fix PLAIN authentication for some IMAP servers (#1488674) - Fix encoding vCard file when contains PHOTO;ENCODING=b (#1488683) diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc index 6ca704998..3231ed644 100644 --- a/program/steps/settings/folders.inc +++ b/program/steps/settings/folders.inc @@ -85,6 +85,11 @@ else if ($RCMAIL->action == 'delete-folder') else { $deleted = $plugin['result']; } + + // #1488692: update session + if ($deleted && $_SESSION['mbox'] === $mbox) { + $RCMAIL->session->remove('mbox'); + } } if ($OUTPUT->ajax_call && $deleted) { @@ -393,15 +398,20 @@ function rcmail_rename_folder($oldname, $newname) foreach ($a_threaded as $key => $val) { if ($key == $oldname) { unset($a_threaded[$key]); - $a_threaded[$newname] = true; + $a_threaded[$newname] = true; } else if (preg_match($oldprefix, $key)) { unset($a_threaded[$key]); - $a_threaded[preg_replace($oldprefix, $newname.$delimiter, $key)] = true; + $a_threaded[preg_replace($oldprefix, $newname.$delimiter, $key)] = true; } } $RCMAIL->user->save_prefs(array('message_threading' => $a_threaded)); + // #1488692: update session + if ($_SESSION['mbox'] === $oldname) { + $_SESSION['mbox'] = $newname; + } + return true; } diff --git a/program/steps/settings/save_folder.inc b/program/steps/settings/save_folder.inc index 73cc5e4bf..877b0fbbe 100644 --- a/program/steps/settings/save_folder.inc +++ b/program/steps/settings/save_folder.inc @@ -1,11 +1,11 @@ show_message('folderupdated', 'confirmation'); + if ($rename) { + // #1488692: update session + if ($_SESSION['mbox'] === $folder['oldname']) { + $_SESSION['mbox'] = $folder['name']; + } rcmail_update_folder_row($folder['name'], $folder['oldname'], $folder['subscribe'], $folder['class']); $OUTPUT->send('iframe'); } -- cgit v1.2.3 From a04a74fec4b5e13e8464f1f3c9071fa0b56a13eb Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 12 Sep 2012 09:59:10 +0200 Subject: Improvements in building criteria string for IMAP SEARCH --- program/include/rcube_imap.php | 8 +++++++- program/include/rcube_shared.inc | 15 +++++++++++++++ program/steps/mail/search.inc | 2 +- tests/Framework/Shared.php | 28 ++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) (limited to 'program/steps') diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 66b5c4bd6..0b2f84d4f 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -1434,6 +1434,12 @@ class rcube_imap extends rcube_storage $criteria = 'UNDELETED '.$criteria; } + // unset CHARSET if criteria string is ASCII, this way + // SEARCH won't be re-sent after "unsupported charset" response + if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) { + $charset = 'US-ASCII'; + } + if ($this->threading) { $threads = $this->conn->thread($folder, $this->threading, $criteria, true, $charset); @@ -1465,7 +1471,7 @@ class rcube_imap extends rcube_storage } $messages = $this->conn->search($folder, - ($charset ? "CHARSET $charset " : '') . $criteria, true); + ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true); // Error, try with US-ASCII (some servers may support only US-ASCII) if ($messages->is_error() && $charset && $charset != 'US-ASCII') { diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc index c15305c08..4577c6df5 100644 --- a/program/include/rcube_shared.inc +++ b/program/include/rcube_shared.inc @@ -254,6 +254,21 @@ function asciiwords($str, $css_id = false, $replace_with = '') } +/** + * Check if a string contains only ascii characters + * + * @param string $str String to check + * @param bool $control_chars Includes control characters + * + * @return bool + */ +function is_ascii($str, $control_chars = true) +{ + $regexp = $control_chars ? '/[^\x00-\x7F]/' : '/[^\x20-\x7E]/'; + return preg_match($regexp, $str) ? false : true; +} + + /** * Remove single and double quotes from a given string * diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index 670680959..db5424b3b 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -100,7 +100,7 @@ $search = isset($srch) ? trim($srch) : trim($str); if (!empty($subject)) { $search_str .= str_repeat(' OR', count($subject)-1); foreach ($subject as $sub) - $search_str .= sprintf(" %s {%d}\r\n%s", $sub, strlen($search), $search); + $search_str .= ' ' . $sub . ' ' . rcube_imap_generic::escape($search); } $search_str = trim($search_str); diff --git a/tests/Framework/Shared.php b/tests/Framework/Shared.php index 99ef829da..0394cd025 100644 --- a/tests/Framework/Shared.php +++ b/tests/Framework/Shared.php @@ -201,4 +201,32 @@ class Framework_Shared extends PHPUnit_Framework_TestCase } + /** + * rcube_shared.inc: is_ascii() + */ + function test_is_ascii() + { + $result = is_ascii("0123456789"); + $this->assertTrue($result, "Valid ASCII (numbers)"); + + $result = is_ascii("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + $this->assertTrue($result, "Valid ASCII (letters)"); + + $result = is_ascii(" !\"#\$%&'()*+,-./:;<=>?@[\\^_`{|}~"); + $this->assertTrue($result, "Valid ASCII (special characters)"); + + $result = is_ascii("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + ."\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"); + $this->assertTrue($result, "Valid ASCII (control characters)"); + + $result = is_ascii("\n", false); + $this->assertFalse($result, "Valid ASCII (control characters)"); + + $result = is_ascii("ż"); + $this->assertFalse($result, "Invalid ASCII (UTF-8 character)"); + + $result = is_ascii("ż", false); + $this->assertFalse($result, "Invalid ASCII (UTF-8 character [2])"); + } + } -- cgit v1.2.3 From fa57c98854972eb93bc7aebd8ef363d56989e0f3 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 17 Sep 2012 10:44:50 +0200 Subject: Don't add inline images from HTML part to the attachments list when forwarding in plain text --- program/steps/mail/compose.inc | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'program/steps') diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 29e12675e..04efe7df5 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -1047,15 +1047,23 @@ function rcmail_remove_signature($body) function rcmail_write_compose_attachments(&$message, $bodyIsHtml) { - global $RCMAIL, $COMPOSE; + global $RCMAIL, $COMPOSE, $compose_mode; $cid_map = $messages = array(); foreach ((array)$message->mime_parts as $pid => $part) { - if (($part->ctype_primary != 'message' || !$bodyIsHtml) && $part->ctype_primary != 'multipart' && - ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) - && $part->mimetype != 'application/ms-tnef' - ) { + if ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) { + if ($part->ctype_primary == 'message' || $part->ctype_primary == 'multipart') { + continue; + } + if ($part->mimetype == 'application/ms-tnef') { + continue; + } + // skip inline images when forwarding in plain text + if ($part->content_id && !$bodyIsHtml && $compose_mode == RCUBE_COMPOSE_FORWARD) { + continue; + } + $skip = false; if ($part->mimetype == 'message/rfc822') { $messages[] = $part->mime_id; -- cgit v1.2.3 From 32ba62889c1def94f555c3e683fc8087ee16c9b3 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 17 Sep 2012 12:23:34 +0200 Subject: Don't directly require email address on contact import, allowing import of contacts being validated by addressbook validation code. The same as for create/edit contact actions. --- program/steps/addressbook/edit.inc | 3 --- program/steps/addressbook/import.inc | 22 +++++++++++++--------- program/steps/addressbook/save.inc | 1 - 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'program/steps') diff --git a/program/steps/addressbook/edit.inc b/program/steps/addressbook/edit.inc index 0f1fd6697..90069a7eb 100644 --- a/program/steps/addressbook/edit.inc +++ b/program/steps/addressbook/edit.inc @@ -117,9 +117,6 @@ function rcmail_contact_editform($attrib) $record = rcmail_get_edit_record(); - // add some labels to client - $RCMAIL->output->add_label('noemailwarning', 'nonamewarning'); - // copy (parsed) address template to client if (preg_match_all('/\{([a-z0-9]+)\}([^{]*)/i', $RCMAIL->config->get('address_template', ''), $templ, PREG_SET_ORDER)) $RCMAIL->output->set_env('address_template', $templ); diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc index 654a33602..15e04b82a 100644 --- a/program/steps/addressbook/import.inc +++ b/program/steps/addressbook/import.inc @@ -189,32 +189,36 @@ if (is_array($_FILES['_file'])) { $IMPORT_STATS->names = array(); $IMPORT_STATS->skipped_names = array(); $IMPORT_STATS->count = count($vcards); - $IMPORT_STATS->inserted = $IMPORT_STATS->skipped = $IMPORT_STATS->nomail = $IMPORT_STATS->errors = 0; + $IMPORT_STATS->inserted = $IMPORT_STATS->skipped = $IMPORT_STATS->invalid = $IMPORT_STATS->errors = 0; if ($replace) { $CONTACTS->delete_all(); } foreach ($vcards as $vcard) { - $email = $vcard->email[0]; $a_record = $vcard->get_assoc(); - // skip entries without an e-mail address or invalid - if (empty($email) || !$CONTACTS->validate($a_record, true)) { - $IMPORT_STATS->nomail++; + // skip invalid (incomplete) entries + if (!$CONTACTS->validate($a_record, true)) { + $IMPORT_STATS->invalid++; continue; } // We're using UTF8 internally + $email = $vcard->email[0]; $email = rcube_idn_to_utf8($email); - if (!$replace && $email) { + if (!$replace) { + $existing = null; // compare e-mail address - $existing = $CONTACTS->search('email', $email, 1, false); - if (!$existing->count && $vcard->displayname) { // compare display name + if ($email) { + $existing = $CONTACTS->search('email', $email, 1, false); + } + // compare display name if email not found + if ((!$existing || !$existing->count) && $vcard->displayname) { $existing = $CONTACTS->search('name', $vcard->displayname, 1, false); } - if ($existing->count) { + if ($existing && $existing->count) { $IMPORT_STATS->skipped++; $IMPORT_STATS->skipped_names[] = $vcard->displayname ? $vcard->displayname : $email; continue; diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc index 3bfce3b4d..887e49827 100644 --- a/program/steps/addressbook/save.inc +++ b/program/steps/addressbook/save.inc @@ -161,7 +161,6 @@ else { $source = $orig_source; // show notice if existing contacts with same e-mail are found - $existing = false; foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) { if ($email && ($res = $CONTACTS->search('email', $email, 1, false, true)) && $res->count) { $OUTPUT->show_message('contactexists', 'notice', null, false); -- cgit v1.2.3