Don't allow TO field to be blank; adjust auto-detection of FROM for reply-all actions...
[squirrelmail.git] / src / compose.php
index 25beaec8f7f3bb1d152bc9be5971082e3aacdbf4..c2f852975cfe89c306406dce801ab1353e7daa8e 100644 (file)
@@ -10,7 +10,7 @@
  *    - Send mail
  *    - Save As Draft
  *
- * @copyright 1999-2010 The SquirrelMail Project Team
+ * @copyright 1999-2013 The SquirrelMail Project Team
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
@@ -196,12 +196,10 @@ function replyAllString($header) {
     $url_replytoallcc = '';
     foreach( $url_replytoall_ar as $email => $personal) {
         if ($personal) {
-            // if personal name contains address separator then surround
-            // the personal name with double quotes.
-            if (strpos($personal,',') !== false) {
-                $personal = '"'.$personal.'"';
-            }
-            $url_replytoallcc .= ", $personal <$email>";
+            // always quote personal name (can't just quote it if
+            // it contains a comma separator, since it might still
+            // be encoded)
+            $url_replytoallcc .= ", \"$personal\" <$email>";
         } else {
             $url_replytoallcc .= ', '. $email;
         }
@@ -417,7 +415,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
@@ -476,13 +474,14 @@ 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'] &&
             $_FILES['attachfile']['tmp_name'] != 'none') {
         $AttachFailure = saveAttachedFiles($session);
     }
+    
     if (checkInput(false) && !isset($AttachFailure)) {
         if ($mailbox == "All Folders") {
             /* We entered compose via the search results page */
@@ -602,7 +601,7 @@ if ($send) {
 
     // validate security token
     //
-    sm_validate_security_token($submitted_token, 3600, TRUE);
+    sm_validate_security_token($submitted_token, -1, TRUE);
 
     if ($compose_new_win == '1') {
         compose_Header($color, $mailbox);
@@ -651,7 +650,7 @@ if ($send) {
 
     // validate security token
     //
-    sm_validate_security_token($submitted_token, 3600, TRUE);
+    sm_validate_security_token($submitted_token, -1, TRUE);
 
     if ($compose_new_win == '1') {
         compose_Header($color, $mailbox);
@@ -667,7 +666,7 @@ elseif (isset($sigappend)) {
 
     // validate security token
     //
-    sm_validate_security_token($submitted_token, 3600, TRUE);
+    sm_validate_security_token($submitted_token, -1, TRUE);
 
     $signature = $idents[$identity]['signature'];
 
@@ -682,7 +681,7 @@ elseif (isset($sigappend)) {
 
     // validate security token
     //
-    sm_validate_security_token($submitted_token, 3600, TRUE);
+    sm_validate_security_token($submitted_token, -1, TRUE);
 
     if ($compose_new_win == '1') {
         compose_Header($color, $mailbox);
@@ -735,6 +734,11 @@ elseif (isset($sigappend)) {
 
     $values = newMail($mailbox,$passed_id,$passed_ent_id, $action, $session);
 
+    // forward as attachment - subject is in the message in session
+    //
+    if ($action == 'forward_as_attachment' && empty($values['subject']))
+        $subject = $composeMessage->rfc822_header->subject;
+
     /* in case the origin is not read_body.php */
     if (isset($send_to)) {
         $values['send_to'] = $send_to;
@@ -772,7 +776,7 @@ function newMail ($mailbox='', $passed_id='', $passed_ent_id='', $action='', $se
         $key, $imapServerAddress, $imapPort, 
         $composeMessage, $body_quote, $request_mdn, $request_dr,
         $mdn_user_support, $languages, $squirrelmail_language,
-        $default_charset;
+        $default_charset, $do_not_reply_to_self;
 
     /*
      * Set $default_charset to correspond with the user's selection
@@ -880,16 +884,11 @@ function newMail ($mailbox='', $passed_id='', $passed_ent_id='', $action='', $se
         if (count($idents) > 1) {
             foreach($idents as $nr=>$data) {
                 $enc_from_name = '"'.$data['full_name'].'" <'. $data['email_address'].'>';
-                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;
             }
         }
@@ -946,6 +945,12 @@ function newMail ($mailbox='', $passed_id='', $passed_ent_id='', $action='', $se
             case ('forward_as_attachment'):
                 $subject = getforwardSubject(decodeHeader($orig_header->subject,false,false,true));
                 $composeMessage = getMessage_RFC822_Attachment($message, $composeMessage, $passed_id, $passed_ent_id, $imapConnection);
+                $subject = decodeHeader($orig_header->subject,false,false,true);
+                $subject = str_replace('"', "'", $subject);
+                $subject = trim($subject);
+                if (substr(strtolower($subject), 0, 4) != 'fwd:') {
+                    $subject = 'Fwd: ' . $subject;
+                }
                 $body = '';
                 break;
             case ('reply_all'):
@@ -954,20 +959,98 @@ function newMail ($mailbox='', $passed_id='', $passed_ent_id='', $action='', $se
                 } else {
                     $send_to_cc = replyAllString($orig_header);
                     $send_to_cc = decodeHeader($send_to_cc,false,false,true);
+                    $send_to_cc = str_replace('""', '"', $send_to_cc);
                 }
             case ('reply'):
                 // skip this if send_to was already set right above here
                 if(!$send_to) {
                     $send_to = $orig_header->reply_to;
                     if (is_array($send_to) && count($send_to)) {
-                        $send_to = $orig_header->getAddr_s('reply_to');
+                        $send_to = $orig_header->getAddr_s('reply_to', ',', FALSE, TRUE);
                     } else if (is_object($send_to)) { /* unneccesarry, just for failsafe purpose */
-                        $send_to = $orig_header->getAddr_s('reply_to');
+                        $send_to = $orig_header->getAddr_s('reply_to', ',', FALSE, TRUE);
                     } else {
-                        $send_to = $orig_header->getAddr_s('from');
+                        $send_to = $orig_header->getAddr_s('from', ',', FALSE, TRUE);
                     }
                 }
                 $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);
@@ -1051,7 +1134,8 @@ function getAttachments($message, &$composeMessage, $passed_id, $entities, $imap
                     $filename = $message->getFilename();
                     break;
             }
-            $filename = str_replace('&#32;', ' ', decodeHeader($filename));
+//FIXME: added three args to the following, so as to set the last one to TRUE, to mimick a fix in 1.4.21 (#2994865), but didn't test this (note that in 1.4.21, the 2nd and 3rd args are FALSE, but here in this code, they weren't being specified (thus defaulting to TRUE), so I don't know if that means this code is outdated and should have been changed to FALSE, FALSE or if this code is completely different and the addition of the TRUE for arg #4 is wrong
+            $filename = str_replace('&#32;', ' ', decodeHeader($filename, true, true, true));
             if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
                     function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_encode')) {
                 $filename =  call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_encode', $filename);
@@ -1172,6 +1256,12 @@ function showInputForm ($session, $values=false) {
     // to do; SquirrelMail itself will add the final "return true".
     // Onsubmit text is enclosed inside of double quotes, so plugins
     // need to quote accordingly.
+    //
+    // Also, plugin authors should try to retain compatibility with
+    // the Compose Extras plugin by resetting its compose submit
+    // counter when preventing form submit.  Use this code: 
+    // if (your-code-here) { submit_count = 0; return false; }
+    //
     if (checkForJavascript()) {
         if (empty($compose_onsubmit))
             $compose_onsubmit = array();
@@ -1255,10 +1345,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...
     //
@@ -1294,9 +1384,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');
@@ -1305,7 +1395,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);
@@ -1614,6 +1704,8 @@ function deliverMessage(&$composeMessage, $draft=false) {
 
     $reply_to = '';
     $reply_to  = $idents[$identity]['reply_to'];
+    if ($reply_to && strpos($reply_to, '@') === FALSE)
+        $reply_to .= '@' . $domain;
     
     $from_addr = build_from_header($identity);
     $rfc822_header->from = $rfc822_header->parseAddress($from_addr,true);
@@ -1718,7 +1810,7 @@ function deliverMessage(&$composeMessage, $draft=false) {
             $composeMessage->purgeAttachments();
             return $success;
         } else {
-            $msg  = '<br />'.sprintf(_("Error: Draft folder %s does not exist."), htmlspecialchars($draft_folder));
+            $msg  = '<br />'.sprintf(_("Error: Draft folder %s does not exist."), sm_encode_html_special_chars($draft_folder));
             plain_error_message($msg);
             return false;
         }
@@ -1730,14 +1822,16 @@ function deliverMessage(&$composeMessage, $draft=false) {
     }
     if (!$success) {
         // $deliver->dlv_server_msg is not always server's reply
-        $msg = _("Message not sent.") . "<br />\n" .
-            $deliver->dlv_msg;
+        $msg = _("Message not sent.")
+             . "<br />\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.='<br />' .
-                _("Server replied:") . ' ' . $deliver->dlv_ret_nr . ' ' .
-                nl2br(htmlspecialchars($deliver->dlv_server_msg));
+            $msg .= '<br />'
+                  . _("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 {