1 <?php
  2 /**
  3  * @package     Joomla.Platform
  4  * @subpackage  Mail
  5  *
  6  * @copyright   Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
  7  * @license     GNU General Public License version 2 or later; see LICENSE
  8  */
  9 
 10 defined('JPATH_PLATFORM') or die;
 11 
 12 /**
 13  * Email helper class, provides static methods to perform various tasks relevant
 14  * to the Joomla email routines.
 15  *
 16  * TODO: Test these methods as the regex work is first run and not tested thoroughly
 17  *
 18  * @since  11.1
 19  */
 20 abstract class JMailHelper
 21 {
 22     /**
 23      * Cleans single line inputs.
 24      *
 25      * @param   string  $value  String to be cleaned.
 26      *
 27      * @return  string  Cleaned string.
 28      *
 29      * @since   11.1
 30      */
 31     public static function cleanLine($value)
 32     {
 33         $value = JStringPunycode::emailToPunycode($value);
 34 
 35         return trim(preg_replace('/(%0A|%0D|\n+|\r+)/i', '', $value));
 36     }
 37 
 38     /**
 39      * Cleans multi-line inputs.
 40      *
 41      * @param   string  $value  Multi-line string to be cleaned.
 42      *
 43      * @return  string  Cleaned multi-line string.
 44      *
 45      * @since   11.1
 46      */
 47     public static function cleanText($value)
 48     {
 49         return trim(preg_replace('/(%0A|%0D|\n+|\r+)(content-type:|to:|cc:|bcc:)/i', '', $value));
 50     }
 51 
 52     /**
 53      * Cleans any injected headers from the email body.
 54      *
 55      * @param   string  $body  email body string.
 56      *
 57      * @return  string  Cleaned email body string.
 58      *
 59      * @since   11.1
 60      */
 61     public static function cleanBody($body)
 62     {
 63         // Strip all email headers from a string
 64         return preg_replace("/((From:|To:|Cc:|Bcc:|Subject:|Content-type:) ([\S]+))/", '', $body);
 65     }
 66 
 67     /**
 68      * Cleans any injected headers from the subject string.
 69      *
 70      * @param   string  $subject  email subject string.
 71      *
 72      * @return  string  Cleaned email subject string.
 73      *
 74      * @since   11.1
 75      */
 76     public static function cleanSubject($subject)
 77     {
 78         return preg_replace("/((From:|To:|Cc:|Bcc:|Content-type:) ([\S]+))/", '', $subject);
 79     }
 80 
 81     /**
 82      * Verifies that an email address does not have any extra headers injected into it.
 83      *
 84      * @param   string  $address  email address.
 85      *
 86      * @return  mixed   email address string or boolean false if injected headers are present.
 87      *
 88      * @since   11.1
 89      */
 90     public static function cleanAddress($address)
 91     {
 92         if (preg_match("[\s;,]", $address))
 93         {
 94             return false;
 95         }
 96 
 97         return $address;
 98     }
 99 
100     /**
101      * Verifies that the string is in a proper email address format.
102      *
103      * @param   string  $email  String to be verified.
104      *
105      * @return  boolean  True if string has the correct format; false otherwise.
106      *
107      * @since   11.1
108      */
109     public static function isEmailAddress($email)
110     {
111         // Split the email into a local and domain
112         $atIndex = strrpos($email, '@');
113         $domain = substr($email, $atIndex + 1);
114         $local = substr($email, 0, $atIndex);
115 
116         // Check Length of domain
117         $domainLen = strlen($domain);
118 
119         if ($domainLen < 1 || $domainLen > 255)
120         {
121             return false;
122         }
123 
124         /*
125          * Check the local address
126          * We're a bit more conservative about what constitutes a "legal" address, that is, a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-
127          * The first and last character in local cannot be a period ('.')
128          * Also, period should not appear 2 or more times consecutively
129          */
130         $allowed = "a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-";
131         $regex = "/^[$allowed][\.$allowed]{0,63}$/";
132 
133         if (!preg_match($regex, $local) || substr($local, -1) == '.' || $local[0] == '.' || preg_match('/\.\./', $local))
134         {
135             return false;
136         }
137 
138         // No problem if the domain looks like an IP address, ish
139         $regex = '/^[0-9\.]+$/';
140 
141         if (preg_match($regex, $domain))
142         {
143             return true;
144         }
145 
146         // Check Lengths
147         $localLen = strlen($local);
148 
149         if ($localLen < 1 || $localLen > 64)
150         {
151             return false;
152         }
153 
154         // Check the domain
155         $domain_array = explode('.', rtrim($domain, '.'));
156         $regex = '/^[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/';
157 
158         foreach ($domain_array as $domain)
159         {
160             // Convert domain to punycode
161             $domain = JStringPunycode::toPunycode($domain);
162 
163             // Must be something
164             if (!$domain)
165             {
166                 return false;
167             }
168 
169             // Check for invalid characters
170             if (!preg_match($regex, $domain))
171             {
172                 return false;
173             }
174 
175             // Check for a dash at the beginning of the domain
176             if (strpos($domain, '-') === 0)
177             {
178                 return false;
179             }
180 
181             // Check for a dash at the end of the domain
182             $length = strlen($domain) - 1;
183 
184             if (strpos($domain, '-', $length) === $length)
185             {
186                 return false;
187             }
188         }
189 
190         return true;
191     }
192 }
193