Fixed bug #8255: Problems with PHP mail function on Windows
authorDmitry Dulepov <dmitry.dulepov@gmail.com>
Tue, 5 Aug 2008 11:24:43 +0000 (11:24 +0000)
committerDmitry Dulepov <dmitry.dulepov@gmail.com>
Tue, 5 Aug 2008 11:24:43 +0000 (11:24 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@3937 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/class.t3lib_div.php
t3lib/class.t3lib_htmlmail.php

index b83d6c9..fc81ec8 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,7 @@
 
        * Fixed bug #8890: Feature: hook in function "getSingleField_SW" before rendering of single fields in tceforms (thanks to Alex Widschwendter)
        * Fixed bug #6992: t3ib_refindex requires t3lib_BEfunc but does not include it
+       * Fixed bug #8255: Problems with PHP mail function on Windows
 
 2008-08-04  Stanislas Rolland  <typo3@sjbr.ca>
 
index 3e4b640..16e65ae 100755 (executable)
@@ -1424,6 +1424,37 @@ final class t3lib_div {
        }
 
        /**
+        * Checks if current e-mail sending method does not accept recipient/sender name
+        * in a call to PHP mail() function. Windows version of mail() and mini_sendmail
+        * program are known not to process such input correctly and they cause SMTP
+        * errors. This function will return true if current mail sending method has
+        * problem with recipient name in recipient/sender argument for mail().
+        *
+        * TODO: 4.3 should have additional configuration variable, which is combined
+        * by || with the rest in this function.
+        *
+        * @return      boolean true if mail() does not accept recipient name
+        */
+       public static function isBrokenEmailEnv() {
+               return TYPO3_OS == 'WIN' || (false !== strpos(ini_get('sendmail_path'), 'mini_sendmail'));
+       }
+
+       /**
+        * Changes from/to arguments for mail() function to work in any environment.
+        *
+        * @param       string  $address        Address to adjust
+        * @return      string  Adjusted address
+        * @see t3lib_::isBrokenEmailEnv()
+        */
+       public static function adjustMailAddressForEnv($address) {
+               if (self::isBrokenEmailEnv() && false !== ($pos1 = strrpos($address, '<'))) {
+                       $pos2 = strpos($address, '>', $pos1);
+                       $address = substr($address, $pos1 + 1, ($pos2 ? $pos2 : strlen($address)) - $pos1 - 1);
+               }
+               return $address;
+       }
+
+       /**
         * Formats a string for output between <textarea>-tags
         * All content outputted in a textarea form should be passed through this function
         * Not only is the content htmlspecialchar'ed on output but there is also a single newline added in the top. The newline is necessary because browsers will ignore the first newline after <textarea> if that is the first character. Therefore better set it!
@@ -4626,12 +4657,16 @@ final class t3lib_div {
                        $charset = $GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'] ? $GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'] : 'ISO-8859-1';
                }
 
+               $email = self::adjustMailAddressForEnv($email);
                if (!$dontEncodeHeader) {
                                // Mail headers must be ASCII, therefore we convert the whole header to either base64 or quoted_printable
                        $newHeaders=array();
                        foreach (explode(chr(10),$headers) as $line)    {       // Split the header in lines and convert each line separately
                                $parts = explode(': ',$line,2); // Field tags must not be encoded
                                if (count($parts)==2)   {
+                                       if (0 == strcasecmp($parts[0], 'from')) {
+                                               $parts[1] = self::adjustMailAddressForEnv($parts[1]);
+                                       }
                                        $parts[1] = t3lib_div::encodeHeader($parts[1],$encoding,$charset);
                                        $newHeaders[] = implode(': ',$parts);
                                } else {
@@ -4671,14 +4706,12 @@ final class t3lib_div {
                        break;
                }
 
-               $linebreak = chr(10);                   // Default line break for Unix systems.
-               if (TYPO3_OS=='WIN')    {
-                       $linebreak = chr(13).chr(10);   // Line break for Windows. This is needed because PHP on Windows systems send mails via SMTP instead of using sendmail, and thus the linebreak needs to be \r\n.
-               }
-
-               $headers=trim(implode($linebreak,t3lib_div::trimExplode(chr(10),$headers,1)));  // Make sure no empty lines are there.
+               // Headers must be separated by CRLF according to RFC 2822, not just LF.
+               // But many servers (Gmail, for example) behave incorectly and want only LF.
+               // So we stick to LF in all cases.
+               $headers = trim(implode(chr(10), t3lib_div::trimExplode(chr(10), $headers, true)));     // Make sure no empty lines are there.
 
-               $ret = @mail($email,$subject,$message,$headers);
+               $ret = @mail($email, $subject, $message, $headers);
                if (!$ret)      {
                        t3lib_div::sysLog('Mail to "'.$email.'" could not be sent (Subject: "'.$subject.'").', 'Core', 3);
                }
index 4d03b09..894fa31 100755 (executable)
@@ -476,7 +476,7 @@ class t3lib_htmlmail {
 
                        // From
                if ($this->from_email) {
-                       if ($this->from_name) {
+                       if ($this->from_name && !t3lib_div::isBrokenEmailEnvironment()) {
                                $this->add_header('From: '.$this->from_name.' <'.$this->from_email.'>');
                        } else {
                                $this->add_header('From: '.$this->from_email);
@@ -545,17 +545,20 @@ class t3lib_htmlmail {
                $boundary = $this->getBoundary();
 
                        // Setting up headers
-               if (count($this->theParts['attach'])) { // Generate (plain/HTML) / attachments
+               if (count($this->theParts['attach'])) {
+                       // Generate (plain/HTML) / attachments
                        $this->add_header('Content-Type: multipart/mixed;');
-                       $this->add_header(' boundary="'.$boundary.'"');
-                       $this->add_message("This is a multi-part message in MIME format.\n");
+                       $this->add_header(' boundary="' . $boundary . '"');
+                       $this->add_message('This is a multi-part message in MIME format.' . "\n");
                        $this->constructMixed($boundary);
-               } elseif ($this->theParts['html']['content']) { // Generate plain/HTML mail
-                       $this->add_header('Content-Type: '.$this->getHTMLContentType());
-                       $this->add_header(' boundary="'.$boundary.'"');
-                       $this->add_message("This is a multi-part message in MIME format.\n");
+               } elseif ($this->theParts['html']['content']) {
+                       // Generate plain/HTML mail
+                       $this->add_header('Content-Type: ' . $this->getHTMLContentType() . ';');
+                       $this->add_header(' boundary="' . $boundary . '"');
+                       $this->add_message('This is a multi-part message in MIME format.' . "\n");
                        $this->constructHTML($boundary);
-               } else {        // Generate plain only
+               } else {
+                       // Generate plain only
                        $this->add_header($this->plain_text_header);
                        $this->add_message($this->getContent('plain'));
                }
@@ -569,33 +572,35 @@ class t3lib_htmlmail {
         * @return      void
         */
        public function constructMixed($boundary) {
-               $this->add_message("--".$boundary);
+               $this->add_message('--' . $boundary);
 
-               if ($this->theParts['html']['content']) {       // HTML and plain is added
+               if ($this->theParts['html']['content']) {
+                       // HTML and plain is added
                        $newBoundary = $this->getBoundary();
-                       $this->add_message("Content-Type: ".$this->getHTMLContentType());
-                       $this->add_message(' boundary="'.$newBoundary.'"');
+                       $this->add_message('Content-Type: '.$this->getHTMLContentType() . ';');
+                       $this->add_message(' boundary="' . $newBoundary . '"');
                        $this->add_message('');
                        $this->constructHTML($newBoundary);
-               } else {        // Purely plain
+               } else {
+                       // Purely plain
                        $this->add_message($this->plain_text_header);
                        $this->add_message('');
                        $this->add_message($this->getContent('plain'));
                }
-                       // attachments are added
+               // attachments are added
                if (is_array($this->theParts['attach'])) {
-                       foreach($this->theParts['attach'] as $media) {
-                               $this->add_message("--".$boundary);
-                               $this->add_message("Content-Type: ".$media['content_type']);
-                               $this->add_message(' name="'.$media['filename'].'"');
+                       foreach ($this->theParts['attach'] as $media) {
+                               $this->add_message('--' . $boundary);
+                               $this->add_message('Content-Type: ' . $media['content_type'] . ';');
+                               $this->add_message(' name="' . $media['filename'] . '"');
                                $this->add_message('Content-Transfer-Encoding: base64');
                                $this->add_message('Content-Disposition: attachment;');
-                               $this->add_message(' filename="'.$media['filename'].'"');
+                               $this->add_message(' filename="' . $media['filename'] . '"');
                                $this->add_message('');
                                $this->add_message($this->makeBase64($media['content']));
                        }
                }
-               $this->add_message("--".$boundary."--\n");
+               $this->add_message('--' . $boundary . '--' . "\n");
        }
 
 
@@ -606,13 +611,13 @@ class t3lib_htmlmail {
         * @return      void
         */
        public function constructHTML($boundary) {
-                       // If media, then we know, the multipart/related content-type has been set before this function call
+               // If media, then we know, the multipart/related content-type has been set before this function call
                if (count($this->theParts['html']['media'])) {
-                       $this->add_message("--".$boundary);
-                               // HTML has media
+                       $this->add_message('--' . $boundary);
+                       // HTML has media
                        $newBoundary = $this->getBoundary();
                        $this->add_message('Content-Type: multipart/alternative;');
-                       $this->add_message(' boundary="'.$newBoundary.'"');
+                       $this->add_message(' boundary="' . $newBoundary . '"');
                        $this->add_message('Content-Transfer-Encoding: 7bit');
                        $this->add_message('');
 
@@ -620,7 +625,7 @@ class t3lib_htmlmail {
                        $this->constructAlternative($newBoundary);
                        $this->constructHTML_media($boundary);
                } else  {
-                               // if no media, just use the $boundary for adding plaintext/html mix
+                       // if no media, just use the $boundary for adding plaintext/html mix
                        $this->constructAlternative($boundary);
                }
        }
@@ -633,19 +638,19 @@ class t3lib_htmlmail {
         * @return      void
         */
        public function constructAlternative($boundary) {
-               $this->add_message("--".$boundary);
+               $this->add_message('--'.$boundary);
 
                        // plain is added
                $this->add_message($this->plain_text_header);
                $this->add_message('');
                $this->add_message($this->getContent('plain'));
-               $this->add_message("--".$boundary);
+               $this->add_message('--' . $boundary);
 
                        // html is added
                $this->add_message($this->html_text_header);
                $this->add_message('');
                $this->add_message($this->getContent('html'));
-               $this->add_message("--".$boundary."--\n");
+               $this->add_message('--' . $boundary . '--' . "\n");
        }
 
 
@@ -656,20 +661,20 @@ class t3lib_htmlmail {
         * @return      void
         */
        public function constructHTML_media($boundary) {
-                       // media is added
+               // media is added
                if (is_array($this->theParts['html']['media'])) {
                        foreach($this->theParts['html']['media'] as $key => $media) {
-                               if (!$this->mediaList || t3lib_div::inList($this->mediaList,$key)) {
-                                       $this->add_message("--".$boundary);
-                                       $this->add_message('Content-Type: '.$media['ctype']);
-                                       $this->add_message('Content-ID: <part'.$key.'.'.$this->messageid.'>');
+                               if (!$this->mediaList || t3lib_div::inList($this->mediaList, $key)) {
+                                       $this->add_message('--' . $boundary);
+                                       $this->add_message('Content-Type: ' . $media['ctype']);
+                                       $this->add_message('Content-ID: <part' . $key . '.' . $this->messageid . '>');
                                        $this->add_message('Content-Transfer-Encoding: base64');
                                        $this->add_message('');
                                        $this->add_message($this->makeBase64($media['content']));
                                }
                        }
                }
-               $this->add_message("--".$boundary."--\n");
+               $this->add_message('--' . $boundary . '--' . "\n");
        }
 
 
@@ -708,35 +713,38 @@ class t3lib_htmlmail {
 
                        // On windows the -f flag is not used (specific for Sendmail and Postfix),
                        // but instead the php.ini parameter sendmail_from is used.
-               $returnPath = (strlen($this->returnPath) > 0) ? '-f'.$this->returnPath : '';
+               $returnPath = (strlen($this->returnPath) > 0) ? '-f "' . escapeshellarg($this->returnPath) . '"' : '';
                if($this->returnPath) {
-                       ini_set(sendmail_from, $this->returnPath);
+                       ini_set('sendmail_from', t3lib_div::normalizeMailAddress($this->returnPath));
                }
+               $recipient = t3lib_div::normalizeMailAddress($this->recipient);
+               $recipient_copy = t3lib_div::normalizeMailAddress($this->recipient_copy);
+
                // If safe mode is on, the fifth parameter to mail is not allowed, so the fix wont work on unix with safe_mode=On
                $returnPathPossible = (!ini_get('safe_mode') && $this->forceReturnPath);
                if ($returnPathPossible) {
-                       $mailWasSent = mail($this->recipient,
+                       $mailWasSent = mail($recipient,
                                  $this->subject,
                                  $this->message,
                                  $this->headers,
                                  $returnPath);
                } else {
-                       $mailWasSent = mail($this->recipient,
+                       $mailWasSent = mail($recipient,
                                  $this->subject,
                                  $this->message,
                                  $this->headers);
                }
 
                        // Sending a copy
-               if ($this->recipient_copy) {
+               if ($recipient_copy) {
                        if ($returnPathPossible) {
-                               $mailWasSent = mail($this->recipient_copy,
+                               $mailWasSent = mail($recipient_copy,
                                        $this->subject,
                                        $this->message,
                                        $this->headers,
                                        $returnPath);
                        } else {
-                               $mailWasSent = mail($this->recipient_copy,
+                               $mailWasSent = mail($recipient_copy,
                                        $this->subject,
                                        $this->message,
                                        $this->headers);
@@ -750,17 +758,17 @@ class t3lib_htmlmail {
                                $mailWasSent = mail($this->from_email,
                                        $theParts[0],
                                        $theParts[1],
-                                       "From: ".$this->recipient,
+                                       'From: ' . $recipient,
                                        $returnPath);
                        } else {
                                $mailWasSent = mail($this->from_email,
                                        $theParts[0],
                                        $theParts[1],
-                                       "From: ".$this->recipient);
+                                       'From: ' . $recipient);
                        }
                }
                if ($this->returnPath) {
-                       ini_restore(sendmail_from);
+                       ini_restore('sendmail_from');
                }
                return $mailWasSent;
        }