X-Git-Url: https://vcs.fsf.org/?p=squirrelmail.git;a=blobdiff_plain;f=src%2Fcompose.php;h=75bbde66db129750e57a74470d39727554cd27be;hp=ff5cb9bc82aabd1bea280eab944de25a1112470b;hb=14095490f3fb6743e538dd464ec217f583a04568;hpb=395c3216ac2791704149054f0569db0be696a003 diff --git a/src/compose.php b/src/compose.php index ff5cb9bc..75bbde66 100644 --- a/src/compose.php +++ b/src/compose.php @@ -10,7 +10,7 @@ * - Send mail * - Save As Draft * - * @copyright 1999-2011 The SquirrelMail Project Team + * @copyright 1999-2021 The SquirrelMail Project Team * @license http://opensource.org/licenses/gpl-license.php GNU Public License * @version $Id$ * @package squirrelmail @@ -41,6 +41,7 @@ require_once(SM_PATH . 'class/deliver/Deliver.class.php'); require_once(SM_PATH . 'functions/addressbook.php'); require_once(SM_PATH . 'functions/forms.php'); require_once(SM_PATH . 'functions/identity.php'); +global $imap_stream_options; // in case not defined in config /* --------------------- Get globals ------------------------------------- */ @@ -54,7 +55,8 @@ sqgetGlobalVar('compose_messages', $compose_messages, SQ_SESSION); // compose_messages only useful in SESSION when a forward-as-attachment // has been preconstructed for us and passed in via that mechanism; once // we have it, we can clear it from the SESSION -sqsession_unregister('compose_messages'); +// -- No, this is useful in other scenarios, too -- removing: +// sqsession_unregister('compose_messages'); // Turn on delayed error handling in case we wind up redirecting below $oErrorHandler->setDelayedErrors(true); @@ -72,6 +74,7 @@ if (isset($send) && $send) { } sqgetGlobalVar('session',$session, $SQ_GLOBAL); sqgetGlobalVar('mailbox',$mailbox, $SQ_GLOBAL); +sqgetGlobalVar('identity',$orig_identity, $SQ_GLOBAL); if(!sqgetGlobalVar('identity',$identity, $SQ_GLOBAL)) { $identity=0; } @@ -172,8 +175,12 @@ function replyAllString($header) { /** * 1) Remove the addresses we'll be sending the message 'to' */ - if (isset($header->reply_to)) { + if (isset($header->reply_to) && is_array($header->reply_to) && count($header->reply_to)) { $excl_ar = $header->getAddr_a('reply_to'); + } else if (is_object($header->reply_to)) { /* unneccesarry, just for failsafe purpose */ + $excl_ar = $header->getAddr_a('reply_to'); + } else { + $excl_ar = $header->getAddr_a('from'); } /** * 2) Remove our identities from the CC list (they still can be in the @@ -244,6 +251,7 @@ function getReplyCitation($orig_from, $orig_date) { $full_reply_citation = sprintf(_("%s wrote:"),$sOrig_from); break; case 'quote_who': + // TODO: the words "quote" and "who" are translated in 1.4.x so why not here? This isn't a real HTML tag... $start = ''; $full_reply_citation = $start . $sOrig_from . $end; @@ -403,8 +411,25 @@ if (!empty($compose_messages[$session])) { // should never directly manipulate an object like this if (!empty($attachments)) { $attachments = unserialize(urldecode($attachments)); - if (!empty($attachments) && is_array($attachments)) - $composeMessage->entities = $attachments; + if (!empty($attachments) && is_array($attachments)) { + // sanitize the "att_local_name" since it is user-supplied and used to access the file system + // it must be alpha-numeric and 32 characters long (see the use of GenerateRandomString() below) + foreach ($attachments as $i => $attachment) { + if (empty($attachment->att_local_name) || strlen($attachment->att_local_name) !== 32) { + unset($attachments[$i]); + continue; + } + // probably marginal difference between (ctype_alnum + function_exists) and preg_match + if (function_exists('ctype_alnum')) { + if (!ctype_alnum($attachment->att_local_name)) + unset($attachments[$i]); + } + else if (preg_match('/[^0-9a-zA-Z]/', $attachment->att_local_name)) + unset($attachments[$i]); + } + if (!empty($attachments)) + $composeMessage->entities = $attachments; + } } if (empty($mailbox)) { @@ -415,7 +440,7 @@ if ($draft) { // validate security token // - sm_validate_security_token($submitted_token, 3600, TRUE); + sm_validate_security_token($submitted_token, -1, TRUE); /* * Set $default_charset to correspond with the user's selection @@ -429,7 +454,7 @@ if ($draft) { $draft_message = _("Draft Email Saved"); /* If this is a resumed draft, then delete the original */ if(isset($delete_draft)) { - $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, false); + $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, false, $imap_stream_options); sqimap_mailbox_select($imap_stream, $draft_folder); // force bypass_trash=true because message should be saved when deliverMessage() returns true. // in current implementation of sqimap_msgs_list_flag() single message id can @@ -474,7 +499,7 @@ if ($send) { // validate security token // - sm_validate_security_token($submitted_token, 3600, TRUE); + sm_validate_security_token($submitted_token, -1, TRUE); if (isset($_FILES['attachfile']) && $_FILES['attachfile']['tmp_name'] && @@ -542,7 +567,7 @@ if ($send) { /* if it is resumed draft, delete draft message */ if ( isset($delete_draft)) { - $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, false); + $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, false, $imap_stream_options); sqimap_mailbox_select($imap_stream, $draft_folder); // bypass_trash=true because message should be saved when deliverMessage() returns true. // in current implementation of sqimap_msgs_list_flag() single message id can @@ -571,8 +596,14 @@ if ($send) { exit(); } else { if ( !isset($pageheader_sent) || !$pageheader_sent ) { - header("Location: $location/right_main.php?mailbox=$urlMailbox". - "&startMessage=$startMessage&mail_sent=$mail_sent"); + global $return_to_message_after_reply; + if (($action === 'reply' || $action === 'reply_all' || $action === 'forward' || $action === 'forward_as_attachment') + && $return_to_message_after_reply && $passed_id) + header("Location: $location/read_body.php?passed_id=$passed_id&mailbox=$urlMailbox". + "&startMessage=$startMessage&mail_sent=$mail_sent"); + else + header("Location: $location/right_main.php?mailbox=$urlMailbox". + "&startMessage=$startMessage&mail_sent=$mail_sent"); } else { //FIXME: DON'T ECHO HTML FROM CORE! echo '

'; - if(strtolower($enc_from_name) == strtolower($orig_from)) { - $identity = $nr; - // don't stop! need to build $identities array for idents match below - //break; - } $identities[] = $enc_from_name; } $identity_match = $orig_header->findAddress($identities); - if ($identity_match) { + if ($identity_match !== FALSE) { $identity = $identity_match; } } @@ -923,6 +955,11 @@ function newMail ($mailbox='', $passed_id='', $passed_ent_id='', $action='', $se // rewrap the body to clean up quotations and line lengths sqBodyWrap($body, $editor_size); $composeMessage = getAttachments($message, $composeMessage, $passed_id, $entities, $imapConnection); + if (!empty($orig_header->x_sm_flag_reply)) + $composeMessage->rfc822_header->more_headers['X-SM-Flag-Reply'] = $orig_header->x_sm_flag_reply; +//TODO: completely unclear if should be using $compose_session instead of $session below + $compose_messages[$session] = $composeMessage; + sqsession_register($compose_messages,'compose_messages'); break; case ('edit_as_new'): $send_to = decodeHeader($orig_header->getAddr_s('to'),false,false,true); @@ -980,6 +1017,82 @@ function newMail ($mailbox='', $passed_id='', $passed_ent_id='', $action='', $se } $send_to = decodeHeader($send_to,false,false,true); $send_to = str_replace('""', '"', $send_to); + + + // If user doesn't want replies to her own messages + // going back to herself (instead send again to the + // original recipient of the message being replied to), + // then iterate through identities, checking if the TO + // field is one of them (if the reply is to ourselves) + // + // Note we don't bother if the original message doesn't + // have anything in the TO field itself (because that's + // what we use if we change the recipient to be that of + // the previous message) + // + if ($do_not_reply_to_self && !empty($orig_header->to)) { + + $orig_to = ''; + + foreach($idents as $id) { + + if (!empty($id['email_address']) + && strpos($send_to, $id['email_address']) !== FALSE) { + + // if this is a reply-all, the original recipient + // is already in the CC field, so we can just blank + // the recipient (TO field) (as long as the CC field + // isn't empty that is)... but then move the CC into + // the TO, so TO isn't empty + // + if ($action == 'reply_all' && !empty($send_to_cc)) { + $orig_to = $send_to_cc; + $send_to_cc = ''; + break; + } + + $orig_to = $orig_header->to; + if (is_array($orig_to) && count($orig_to)) { + $orig_to = $orig_header->getAddr_s('to', ',', FALSE, TRUE); + } else if (is_object($orig_to)) { /* unneccesarry, just for failsafe purpose */ + $orig_to = $orig_header->getAddr_s('to', ',', FALSE, TRUE); + } else { + $orig_to = ''; + } + $orig_to = decodeHeader($orig_to,false,false,true); + $orig_to = str_replace('""', '"', $orig_to); + + break; + } + } + + // if the reply was addressed back to ourselves, + // we will send it to the TO of the previous message + // + if (!empty($orig_to)) { + + $send_to = $orig_to; + + // in this case, we also want to reset the FROM + // identity as well (it should match the original + // *FROM* header instead of TO or CC) + // + if (count($idents) > 1) { + $identity = ''; + foreach($idents as $i => $id) { + if (!empty($id['email_address']) + && strpos($orig_from, $id['email_address']) !== FALSE) { + $identity = $i; + break; + } + } + } + + } + + } + + $subject = decodeHeader($orig_header->subject,false,false,true); $subject = str_replace('"', "'", $subject); $subject = trim($subject); @@ -1274,10 +1387,10 @@ function showInputForm ($session, $values=false) { $oTemplate->assign('identity_def', $identity); $oTemplate->assign('input_onfocus', 'onfocus="'.join(' ', $onfocus_array).'"'); - $oTemplate->assign('to', htmlspecialchars($send_to)); - $oTemplate->assign('cc', htmlspecialchars($send_to_cc)); - $oTemplate->assign('bcc', htmlspecialchars($send_to_bcc)); - $oTemplate->assign('subject', htmlspecialchars($subject)); + $oTemplate->assign('to', sm_encode_html_special_chars($send_to)); + $oTemplate->assign('cc', sm_encode_html_special_chars($send_to_cc)); + $oTemplate->assign('bcc', sm_encode_html_special_chars($send_to_bcc)); + $oTemplate->assign('subject', sm_encode_html_special_chars($subject)); // access keys... // @@ -1313,9 +1426,9 @@ function showInputForm ($session, $values=false) { } else { $body_str = "\n\n".($prefix_sig==true? "-- \n":'').decodeHeader($signature,false,false); } - $body_str .= "\n\n".htmlspecialchars(decodeHeader($body,false,false)); + $body_str .= "\n\n".sm_encode_html_special_chars(decodeHeader($body,false,false)); } else { - $body_str = "\n\n".htmlspecialchars(decodeHeader($body,false,false)); + $body_str = "\n\n".sm_encode_html_special_chars(decodeHeader($body,false,false)); // FIXME: test is specific to ja_JP translation implementation. See above comments. if ($default_charset == 'iso-2022-jp') { $body_str .= "\n\n".($prefix_sig==true? "-- \n":'').mb_convert_encoding($signature, 'EUC-JP'); @@ -1324,7 +1437,7 @@ function showInputForm ($session, $values=false) { } } } else { - $body_str = htmlspecialchars(decodeHeader($body,false,false)); + $body_str = sm_encode_html_special_chars(decodeHeader($body,false,false)); } $oTemplate->assign('editor_width', (int)$editor_size); @@ -1371,7 +1484,9 @@ function showInputForm ($session, $values=false) { } $attach = array(); - global $username, $attachment_dir; + global $username, $attachment_dir, $upload_filesize_divisor; + if (empty($upload_filesize_divisor)) + $upload_filesize_divisor = 1000; // *not* 1024 -- does this break for some users? $hashed_attachment_dir = getHashedDir($username, $attachment_dir); if (!empty($attach_array)) { foreach ($attach_array as $key => $attachment) { @@ -1394,6 +1509,7 @@ function showInputForm ($session, $values=false) { $max = min($sizes); $oTemplate->assign('max_file_size', empty($max) ? -1 : $max); $oTemplate->assign('attachments', $attach); + $oTemplate->assign('upload_filesize_divisor', $upload_filesize_divisor); // access keys... // @@ -1606,10 +1722,10 @@ function getByteSize($ini_size) { */ function deliverMessage(&$composeMessage, $draft=false) { global $send_to, $send_to_cc, $send_to_bcc, $mailprio, $subject, $body, - $username, $identity, $idents, $data_dir, + $username, $identity, $idents, $data_dir, $compose_messages, $session, $request_mdn, $request_dr, $default_charset, $useSendmail, $domain, $action, $default_move_to_sent, $move_to_sent, - $imapServerAddress, $imapPort, $sent_folder, $key; + $imapServerAddress, $imapPort, $imap_stream_options, $sent_folder, $key; $rfc822_header = $composeMessage->rfc822_header; @@ -1633,7 +1749,7 @@ function deliverMessage(&$composeMessage, $draft=false) { $reply_to = ''; $reply_to = $idents[$identity]['reply_to']; - if (strpos($reply_to, '@') === FALSE) + if ($reply_to && strpos($reply_to, '@') === FALSE) $reply_to .= '@' . $domain; $from_addr = build_from_header($identity); @@ -1702,17 +1818,30 @@ function deliverMessage(&$composeMessage, $draft=false) { it over to deliver; plugin authors note that $composeMessage is sent and modified by reference since 1.5.2 */ do_hook('compose_send', $composeMessage); +//TODO: need to migrate to the following, but it neessitates changes in existing plugins, since the args are now an array + //$temp = array(&$composeMessage, &$draft); + //do_hook('compose_send', $temp); + + // remove special header if present and prepare to mark + // a message that a draft was composed in reply to + if (!empty($composeMessage->rfc822_header->x_sm_flag_reply) && !$draft) { + global $passed_id, $mailbox; + // tricks the code below that marks the reply + list($action, $passed_id, $mailbox) = explode('::', $rfc822_header->x_sm_flag_reply, 3); + unset($composeMessage->rfc822_header->x_sm_flag_reply); + unset($composeMessage->rfc822_header->more_headers['X-SM-Flag-Reply']); + } if (!$useSendmail && !$draft) { require_once(SM_PATH . 'class/deliver/Deliver_SMTP.class.php'); $deliver = new Deliver_SMTP(); - global $smtpServerAddress, $smtpPort, $pop_before_smtp, $pop_before_smtp_host; + global $smtpServerAddress, $smtpPort, $smtp_stream_options, $pop_before_smtp, $pop_before_smtp_host; $authPop = (isset($pop_before_smtp) && $pop_before_smtp) ? true : false; if (empty($pop_before_smtp_host)) $pop_before_smtp_host = $smtpServerAddress; get_smtp_user($user, $pass); $stream = $deliver->initStream($composeMessage,$domain,0, - $smtpServerAddress, $smtpPort, $user, $pass, $authPop, $pop_before_smtp_host); + $smtpServerAddress, $smtpPort, $user, $pass, $authPop, $pop_before_smtp_host, $smtp_stream_options); } elseif (!$draft) { require_once(SM_PATH . 'class/deliver/Deliver_SendMail.class.php'); global $sendmail_path, $sendmail_args; @@ -1729,17 +1858,27 @@ function deliverMessage(&$composeMessage, $draft=false) { } elseif ($draft) { global $draft_folder; $imap_stream = sqimap_login($username, false, $imapServerAddress, - $imapPort, 0); + $imapPort, 0, $imap_stream_options); if (sqimap_mailbox_exists ($imap_stream, $draft_folder)) { +//TODO: this can leak private information about folders and message IDs if messages are accessed/sent from another client --- should this feature be optional? + // make note of the message to mark as having been replied to + global $passed_id, $mailbox; + if ($action == 'reply' || $action == 'reply_all' || $action == 'forward' || $action == 'forward_as_attachment') { + $composeMessage->rfc822_header->more_headers['X-SM-Flag-Reply'] = $action . '::' . $passed_id . '::' . $mailbox; + } + require_once(SM_PATH . 'class/deliver/Deliver_IMAP.class.php'); $imap_deliver = new Deliver_IMAP(); $success = $imap_deliver->mail($composeMessage, $imap_stream, $reply_id, $reply_ent_id, $imap_stream, $draft_folder); sqimap_logout($imap_stream); unset ($imap_deliver); $composeMessage->purgeAttachments(); +//TODO: completely unclear if should be using $compose_session instead of $session below + unset($compose_messages[$session]); + sqsession_register($compose_messages,'compose_messages'); return $success; } else { - $msg = '
'.sprintf(_("Error: Draft folder %s does not exist."), htmlspecialchars($draft_folder)); + $msg = '
'.sprintf(_("Error: Draft folder %s does not exist."), sm_encode_html_special_chars($draft_folder)); plain_error_message($msg); return false; } @@ -1751,19 +1890,21 @@ function deliverMessage(&$composeMessage, $draft=false) { } if (!$success) { // $deliver->dlv_server_msg is not always server's reply - $msg = _("Message not sent.") . "
\n" . - $deliver->dlv_msg; + $msg = _("Message not sent.") + . "
\n" + . (isset($deliver->dlv_msg) ? $deliver->dlv_msg : ''); if (!empty($deliver->dlv_server_msg)) { // add 'server replied' part only when it is not empty. // Delivery error can be generated by delivery class itself - $msg.='
' . - _("Server replied:") . ' ' . $deliver->dlv_ret_nr . ' ' . - nl2br(htmlspecialchars($deliver->dlv_server_msg)); + $msg .= '
' + . _("Server replied:") . ' ' + . (isset($deliver->dlv_ret_nr) ? $deliver->dlv_ret_nr . ' ' : '') + . nl2br(sm_encode_html_special_chars($deliver->dlv_server_msg)); } plain_error_message($msg); } else { unset ($deliver); - $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, 0); + $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, 0, $imap_stream_options); // mark as replied or forwarded if applicable @@ -1772,60 +1913,64 @@ function deliverMessage(&$composeMessage, $draft=false) { if ($action=='reply' || $action=='reply_all' || $action=='forward' || $action=='forward_as_attachment') { require(SM_PATH . 'functions/mailbox_display.php'); - $aMailbox = sqm_api_mailbox_select($imap_stream, $iAccount, $mailbox,array('setindex' => $what, 'offset' => $startMessage),array()); - switch($action) { - case 'reply': - case 'reply_all': - // check if we are allowed to set the \\Answered flag - if (in_array('\\answered',$aMailbox['PERMANENTFLAGS'], true)) { - $aUpdatedMsgs = sqimap_toggle_flag($imap_stream, array($passed_id), '\\Answered', true, false); - if (isset($aUpdatedMsgs[$passed_id]['FLAGS'])) { - /** - * Only update the cached headers if the header is - * cached. - */ - if (isset($aMailbox['MSG_HEADERS'][$passed_id])) { - $aMailbox['MSG_HEADERS'][$passed_id]['FLAGS'] = $aMsg['FLAGS']; + // select errors here could be due to a draft reply being sent + // after the original message's mailbox is moved or deleted + $aMailbox = sqm_api_mailbox_select($imap_stream, $iAccount, $mailbox,array('setindex' => $what, 'offset' => $startMessage),array(), false); + // a non-empty return from above means we can proceed + if (!empty($aMailbox)) { + switch($action) { + case 'reply': + case 'reply_all': + // check if we are allowed to set the \\Answered flag + if (in_array('\\answered',$aMailbox['PERMANENTFLAGS'], true)) { + $aUpdatedMsgs = sqimap_toggle_flag($imap_stream, array($passed_id), '\\Answered', true, false); + if (isset($aUpdatedMsgs[$passed_id]['FLAGS'])) { + /** + * Only update the cached headers if the header is + * cached. + */ + if (isset($aMailbox['MSG_HEADERS'][$passed_id])) { + $aMailbox['MSG_HEADERS'][$passed_id]['FLAGS'] = $aMsg['FLAGS']; + } } } - } - break; - case 'forward': - case 'forward_as_attachment': - // check if we are allowed to set the $Forwarded flag (RFC 4550 paragraph 2.8) - if (in_array('$forwarded',$aMailbox['PERMANENTFLAGS'], true) || - in_array('\\*',$aMailbox['PERMANENTFLAGS'])) { - - // when forwarding as an attachment from the message - // list, passed_id is not used, need to get UID(s) - // from the query string - // - if (empty($passed_id) && !empty($fwduid)) - $ids = explode('_', $fwduid); - else - $ids = array($passed_id); - - $aUpdatedMsgs = sqimap_toggle_flag($imap_stream, $ids, '$Forwarded', true, false); - - foreach ($ids as $id) { - if (isset($aUpdatedMsgs[$id]['FLAGS'])) { - if (isset($aMailbox['MSG_HEADERS'][$id])) { - $aMailbox['MSG_HEADERS'][$id]['FLAGS'] = $aMsg['FLAGS']; + break; + case 'forward': + case 'forward_as_attachment': + // check if we are allowed to set the $Forwarded flag (RFC 4550 paragraph 2.8) + if (in_array('$forwarded',$aMailbox['PERMANENTFLAGS'], true) || + in_array('\\*',$aMailbox['PERMANENTFLAGS'])) { + + // when forwarding as an attachment from the message + // list, passed_id is not used, need to get UID(s) + // from the query string + // + if (empty($passed_id) && !empty($fwduid)) + $ids = explode('_', $fwduid); + else + $ids = array($passed_id); + + $aUpdatedMsgs = sqimap_toggle_flag($imap_stream, $ids, '$Forwarded', true, false); + + foreach ($ids as $id) { + if (isset($aUpdatedMsgs[$id]['FLAGS'])) { + if (isset($aMailbox['MSG_HEADERS'][$id])) { + $aMailbox['MSG_HEADERS'][$id]['FLAGS'] = $aMsg['FLAGS']; + } } } } + break; } - break; - } - /** - * Write mailbox with updated seen flag information back to cache. - */ - if(isset($aUpdatedMsgs[$passed_id])) { - $mailbox_cache[$iAccount.'_'.$aMailbox['NAME']] = $aMailbox; - sqsession_register($mailbox_cache,'mailbox_cache'); + /** + * Write mailbox with updated seen flag information back to cache. + */ + if(isset($aUpdatedMsgs[$passed_id])) { + $mailbox_cache[$iAccount.'_'.$aMailbox['NAME']] = $aMailbox; + sqsession_register($mailbox_cache,'mailbox_cache'); + } } - } @@ -1868,6 +2013,9 @@ function deliverMessage(&$composeMessage, $draft=false) { // final cleanup // $composeMessage->purgeAttachments(); +//TODO: completely unclear if should be using $compose_session instead of $session below + unset($compose_messages[$session]); + sqsession_register($compose_messages,'compose_messages'); sqimap_logout($imap_stream); }