Aleksander Machniak
2013-10-03 eafd5b1aa4e67c4de18fc09493540b55dc647220
Improved mailto: link arguments handling (#1489363)
5 files modified
124 ■■■■ changed files
CHANGELOG 1 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_utils.php 5 ●●●●● patch | view | raw | blame | history
program/steps/mail/compose.inc 77 ●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 35 ●●●● patch | view | raw | blame | history
program/steps/mail/sendmail.inc 6 ●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Improved mailto: link arguments handling (#1489363)
- Use DOMDocument LIBXML_PARSEHUGE and LIBXML_COMPACT options if possible (#1489302)
- Support HTTP_HOST, SERVER_NAME and SERVER_ADDR values in include_host_config feature
- Hide Delivery Status Notification option when smtp_server is unset (#1489336)
program/lib/Roundcube/rcube_utils.php
@@ -392,10 +392,11 @@
     *
     * @param int    $mode   Source to get value from (GPC)
     * @param string $ignore PCRE expression to skip parameters by name
     * @param boolean $allow_html Allow HTML tags in field value
     *
     * @return array Hash array with all request parameters
     */
    public static function request2param($mode = null, $ignore = 'task|action')
    public static function request2param($mode = null, $ignore = 'task|action', $allow_html = false)
    {
        $out = array();
        $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
@@ -403,7 +404,7 @@
        foreach (array_keys($src) as $key) {
            $fname = $key[0] == '_' ? substr($key, 1) : $key;
            if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) {
                $out[$fname] = self::get_input_value($key, $mode);
                $out[$fname] = self::get_input_value($key, $mode, $allow_html);
            }
        }
program/steps/mail/compose.inc
@@ -54,30 +54,12 @@
  $COMPOSE_ID = uniqid(mt_rand());
  $_SESSION['compose_data_'.$COMPOSE_ID] = array(
    'id'      => $COMPOSE_ID,
    'param'   => request2param(RCUBE_INPUT_GET),
    'param'   => rcube_utils::request2param(RCUBE_INPUT_GET, 'task|action', true),
    'mailbox' => $RCMAIL->storage->get_folder(),
  );
  $COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
  // process values like "mailto:foo@bar.com?subject=new+message&cc=another"
  if ($COMPOSE['param']['to']) {
    // #1486037: remove "mailto:" prefix
    $COMPOSE['param']['to'] = preg_replace('/^mailto:/i', '', $COMPOSE['param']['to']);
    $mailto = explode('?', $COMPOSE['param']['to']);
    if (count($mailto) > 1) {
      $COMPOSE['param']['to'] = $mailto[0];
      parse_str($mailto[1], $query);
      foreach ($query as $f => $val)
        $COMPOSE['param'][$f] = $val;
    }
  }
  // select folder where to save the sent message
  $COMPOSE['param']['sent_mbox'] = $RCMAIL->config->get('sent_mbox');
  // pipe compose parameters thru plugins
  $plugin = $RCMAIL->plugins->exec_hook('message_compose', $COMPOSE);
  $COMPOSE['param'] = array_merge($COMPOSE['param'], $plugin['param']);
  rcmail_process_compose_params($COMPOSE);
  // add attachments listed by message_compose hook
  if (is_array($plugin['attachments'])) {
@@ -260,6 +242,14 @@
}
else {
  $MESSAGE = new stdClass();
  // apply mailto: URL parameters
  if (!empty($COMPOSE['param']['in-reply-to'])) {
    $COMPOSE['reply_msgid'] = '<' . $COMPOSE['param']['in-reply-to'] . '>';
  }
  if (!empty($COMPOSE['param']['references'])) {
    $COMPOSE['references'] = $COMPOSE['param']['references'];
  }
}
$MESSAGE->compose = array();
@@ -414,6 +404,53 @@
/****** compose mode functions ********/
// process compose request parameters
function rcmail_process_compose_params(&$COMPOSE)
{
  if ($COMPOSE['param']['to']) {
    $mailto = explode('?', $COMPOSE['param']['to'], 2);
    // #1486037: remove "mailto:" prefix
    $COMPOSE['param']['to'] = preg_replace('/^mailto:/i', '', $mailto[0]);
    // Supported case-insensitive tokens in mailto URL
    $url_tokens = array('to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'body');
    if (!empty($mailto[1])) {
      parse_str($mailto[1], $query);
      foreach ($query as $f => $val) {
        if (($key = array_search(strtolower($f), $url_tokens)) !== false) {
          $f = $url_tokens[$key];
        }
        // merge mailto: addresses with addresses from 'to' parameter
        if ($f == 'to' && !empty($COMPOSE['param']['to'])) {
          $to_addresses  = rcube_mime::decode_address_list($COMPOSE['param']['to'], null, true, null, true);
          $add_addresses = rcube_mime::decode_address_list($val, null, true);
          foreach ($add_addresses as $addr) {
            if (!in_array($addr['mailto'], $to_addresses)) {
              $to_addresses[] = $addr['mailto'];
              $COMPOSE['param']['to'] = (!empty($to_addresses) ? ', ' : '') . $addr['string'];
            }
          }
        }
        else {
          $COMPOSE['param'][$f] = $val;
        }
      }
    }
  }
  $RCMAIL = rcmail::get_instance();
  // select folder where to save the sent message
  $COMPOSE['param']['sent_mbox'] = $RCMAIL->config->get('sent_mbox');
  // pipe compose parameters thru plugins
  $plugin = $RCMAIL->plugins->exec_hook('message_compose', $COMPOSE);
  $COMPOSE['param'] = array_merge($COMPOSE['param'], $plugin['param']);
}
function rcmail_compose_headers($attrib)
{
  global $MESSAGE;
program/steps/mail/func.inc
@@ -1383,9 +1383,6 @@
{
  global $RCMAIL;
  // Support unicode/punycode in top-level domain part
  $EMAIL_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))';
  $tag    = strtolower($matches[1]);
  $attrib = parse_attrib_string($matches[2]);
  $end    = '>';
@@ -1400,12 +1397,36 @@
    $attrib['href'] = $RCMAIL->url(array('task' => 'utils', 'action' => 'modcss', 'u' => $tempurl, 'c' => $GLOBALS['rcmail_html_container_id']));
    $end = ' />';
  }
  else if (preg_match('/^mailto:'.$EMAIL_PATTERN.'(\?[^"\'>]+)?/i', $attrib['href'], $mailto)) {
    $attrib['href'] = $mailto[0];
  else if (preg_match('/^mailto:(.+)/i', $attrib['href'], $mailto)) {
    list($mailto, $url) = explode('?', html_entity_decode($mailto[1], ENT_QUOTES, 'UTF-8'), 2);
    $url       = urldecode($url);
    $mailto    = urldecode($mailto);
    $addresses = rcube_mime::decode_address_list($mailto, null, true);
    $mailto    = array();
    // do sanity checks on recipients
    foreach ($addresses as $idx => $addr) {
      if (rcube_utils::check_email($addr['mailto'], false)) {
        $addresses[$idx] = $addr['mailto'];
        $mailto[] = $addr['string'];
      }
      else {
        unset($addresses[$idx]);
      }
    }
    if (!empty($addresses)) {
      $attrib['href'] = 'mailto:' . implode(',', $addresses);
    $attrib['onclick'] = sprintf(
      "return %s.command('compose','%s',this)",
      JS_OBJECT_NAME,
      JQ($mailto[1].$mailto[3]));
        JQ(implode(',', $mailto) . ($url ? "?$url" : '')));
    }
    else {
      $attrib['href'] = '#NOP';
      $attrib['onclick'] = '';
    }
  }
  else if (empty($attrib['href']) && !$attrib['name']) {
    $attrib['href'] = './#NOP';
@@ -1476,7 +1497,7 @@
      if ($linked) {
        $attrs = array(
           'href' => 'mailto:' . $mailto,
           'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)),
           'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ(format_email_recipient($mailto, $name))),
           'class' => "rcmContactAddress",
        );
program/steps/mail/sendmail.inc
@@ -414,9 +414,6 @@
if (!empty($_POST['_followupto'])) {
  $headers['Mail-Followup-To'] = rcmail_email_input_format(get_input_value('_followupto', RCUBE_INPUT_POST, TRUE, $message_charset));
}
if (!empty($COMPOSE['reply_msgid'])) {
  $headers['In-Reply-To'] = $COMPOSE['reply_msgid'];
}
// remember reply/forward UIDs in special headers
if (!empty($COMPOSE['reply_uid']) && $savedraft) {
@@ -426,6 +423,9 @@
  $headers['X-Draft-Info'] = array('type' => 'forward', 'uid' => $COMPOSE['forward_uid']);
}
if (!empty($COMPOSE['reply_msgid'])) {
  $headers['In-Reply-To'] = $COMPOSE['reply_msgid'];
}
if (!empty($COMPOSE['references'])) {
  $headers['References'] = $COMPOSE['references'];
}