- Timestamp:
- Jul 24, 2022 4:01:37 PM (20 months ago)
- Location:
- trunk/lib
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/lib/Email.inc.php
r770 r774 61 61 'from' => null, 62 62 'subject' => null, 63 'headers' => null,63 'headers' => [], 64 64 'envelope_sender_address' => null, // AKA the bounce-to address. Will default to 'from' if left null. 65 65 'regex' => null, … … 86 86 // String that contains the email body after replacements. 87 87 protected $_template_replaced; 88 89 // String that contains the final email body. 90 // Use only when email is manually composed and encoded outside of this Email class, and need to bypass replacements, detection of mail header injection attacks, and the encoding provided by mb_send_mail(). 91 protected $_raw_body; 88 92 89 93 // Email debug modes. … … 175 179 } 176 180 181 /* 182 * Append headers to $this->_params['headers'] 183 * 184 * @access public 185 * @param array $headers Array of key=>val pairs that will be appended. 186 * @return void 187 * @author Quinn Comendant <quinn@strangecode.com> 188 * @since 23 Jul 2022 13:36:21 189 */ 190 public function appendHeaders($headers) 191 { 192 $app =& App::getInstance(); 193 if (!isset($headers) || !is_array($headers)) { 194 $app->logMsg(sprintf('%s requires an array of header values', __METHOD__), LOG_NOTICE, __FILE__, __LINE__); 195 } 196 197 $this->_params['headers'] = array_merge($this->_params['headers'], $headers); 198 } 199 177 200 /** 178 201 * Loads template from file to generate email body. … … 188 211 189 212 // Load file, using include_path. 190 if (!$this->_template = file_get_contents($template, true)) { 191 $app->logMsg(sprintf('Email template file does not exist: %s', $template), LOG_ERR, __FILE__, __LINE__); 213 $this->_template = file_get_contents($template, true); 214 if (!$this->_template || '' == trim($this->_template)) { 215 $app->logMsg(sprintf('Email template file does not exist or is empty: %s', $template), LOG_ERR, __FILE__, __LINE__); 192 216 $this->_template = null; 193 217 $this->_template_replaced = null; 218 $this->_raw_body = null; 194 219 return false; 195 220 } … … 203 228 // This could be a new template, so reset the _template_replaced. 204 229 $this->_template_replaced = null; 230 $this->_raw_body = null; 205 231 206 232 $this->_template_filename = $template; … … 213 239 * 214 240 * @access public 215 * @param string $ template Filenameof email template.241 * @param string $string Content of email template. 216 242 * @author Quinn Comendant <quinn@strangecode.com> 217 243 * @since 28 Nov 2005 12:56:23 … … 222 248 223 249 if ('' == trim($string)) { 224 $app->logMsg(sprintf('Empty string provided .', null), LOG_ERR, __FILE__, __LINE__);250 $app->logMsg(sprintf('Empty string provided to %s.', __METHOD__), LOG_ERR, __FILE__, __LINE__); 225 251 $this->_template_replaced = null; 252 $this->_raw_body = null; 226 253 return false; 227 254 } else { … … 229 256 // This could be a new template, so reset the _template_replaced. 230 257 $this->_template_replaced = null; 258 $this->_raw_body = null; 231 259 232 260 $this->_template_filename = '(using Email::setString)'; … … 271 299 // Search and replace all values at once. 272 300 $this->_template_replaced = str_replace($search, $replace, $this->_template); 301 } 302 303 /** 304 * Set the final email body to send. Using this disables any further post-processing, including encoding and scanning for mail header injection attacks. 305 * 306 * @access public 307 * @param string $body Final email body. 308 * @author Quinn Comendant <quinn@strangecode.com> 309 * @since 23 Jul 2022 12:01:52 310 */ 311 public function setRawBody($string) 312 { 313 $app =& App::getInstance(); 314 315 if ('' == trim($string)) { 316 $app->logMsg(sprintf('Empty string provided to %s.', __METHOD__), LOG_ERR, __FILE__, __LINE__); 317 $this->_template = null; 318 $this->_template_replaced = null; 319 $this->_raw_body = null; 320 return false; 321 } else { 322 $this->_template = null; 323 $this->_template_replaced = null; 324 $this->_raw_body = $string; 325 326 $this->_template_filename = '(using Email::setRawBody)'; 327 328 return true; 329 } 273 330 } 274 331 … … 290 347 $app =& App::getInstance(); 291 348 349 if (isset($this->_raw_body)) { 350 return $this->_raw_body; 351 } 352 292 353 $final_body = isset($this->_template_replaced) ? $this->_template_replaced : $this->_template; 293 354 // Ensure all placeholders have been replaced. Find anything with {...} characters. … … 332 393 $app->logMsg('Cannot send email. SUBJECT not defined.', LOG_ERR, __FILE__, __LINE__); 333 394 return false; 334 } else if (!isset($this->_template) ) {335 $app->logMsg(sprintf('Cannot send email: "%s". Template not set.', $this->_params['subject']), LOG_ERR, __FILE__, __LINE__);395 } else if (!isset($this->_template) && !isset($this->_raw_body)) { 396 $app->logMsg(sprintf('Cannot send email: "%s". Need a template or raw body.', $this->_params['subject']), LOG_ERR, __FILE__, __LINE__); 336 397 return false; 337 398 } else if (!isset($this->_params['to'])) { … … 343 404 } 344 405 345 // Wrap email text body, using _template_replaced if replacements have been used, or just a fresh _template if not. 346 $final_body = isset($this->_template_replaced) ? $this->_template_replaced : $this->_template; 347 if (false !== $this->getParam('wrap')) { 348 $final_body = wordwrap($final_body, $this->getParam('line_length'), $this->getParam('crlf')); 349 } 350 351 // Ensure all placeholders have been replaced. Find anything with {...} characters. 352 if (preg_match('/({[^}]+})/', $final_body, $unreplaced_match)) { 353 unset($unreplaced_match[0]); 354 $app->logMsg(sprintf('Unreplaced variable(s) "%s" in template "%s"', getDump($unreplaced_match), $this->_template_filename), LOG_ERR, __FILE__, __LINE__); 355 return false; 356 } 357 358 // Final "to" header can have multiple addresses if in an array. 406 // Final âtoâ header can have multiple addresses if in an array. 359 407 $final_to = is_array($this->_params['to']) ? join(', ', $this->_params['to']) : $this->_params['to']; 360 361 // From headers are custom headers. 362 $headers = array('From' => $this->_params['from']); 363 364 // Additional headers. 365 if (isset($this->_params['headers']) && is_array($this->_params['headers'])) { 366 $headers = array_merge($this->_params['headers'], $headers); 367 } 408 if (!preg_match('/(?:>$|>,)/', $final_to)) { 409 $app->logMsg(sprintf('Email addresses should be enclosed in <angle brackets>: %s', $final_to), LOG_NOTICE, __FILE__, __LINE__); 410 } 411 412 // âFromâ headers are custom headers. 413 $this->appendHeaders(['From' => $this->_params['from']]); 368 414 369 415 // Process headers. 370 416 $final_headers_arr = array(); 371 417 $final_headers = ''; 372 foreach ($ headersas $key => $val) {418 foreach ($this->_params['headers'] as $key => $val) { 373 419 // Validate key and values. 374 420 if (!isset($val) || !strlen($val)) { … … 376 422 continue; 377 423 } 378 if (!strlen($key) || preg_match("/[\n\r]/", $key . $val) || preg_match('/[^\w-]/', $key)) { 424 // Ensure headers meet RFC requirements. 425 // https://datatracker.ietf.org/doc/html/rfc5322#section-2.1.1 426 // https://datatracker.ietf.org/doc/html/rfc5322#section-2.2 427 if (!strlen($key) || strlen($key . $val) > 998 || preg_match('/[^\x21-\x39\x3B-\x7E]/', $key)) { 379 428 $app->logMsg(sprintf('Broken email header provided: %s=%s', $key, $val), LOG_WARNING, __FILE__, __LINE__); 380 429 continue; … … 407 456 } 408 457 409 // Check for mail header injection attacks. 410 $full_mail_content = join($this->getParam('crlf'), array($final_to, $this->_params['subject'], $final_body)); 411 if (preg_match("/(^|[\n\r])(Content-Type|MIME-Version|Content-Transfer-Encoding|Bcc|Cc)\s*:/i", $full_mail_content)) { 412 $app->logMsg(sprintf('Mail header injection attack in content: %s', $full_mail_content), LOG_WARNING, __FILE__, __LINE__); 413 return false; 458 if (isset($this->_raw_body)) { 459 $mail_function = 'mail'; 460 $final_body = $this->_raw_body; 461 } else { 462 $mail_function = 'mb_send_mail'; 463 464 // Wrap email text body, using _template_replaced if replacements have been used, or just a fresh _template if not. 465 $final_body = isset($this->_template_replaced) ? $this->_template_replaced : $this->_template; 466 if (false !== $this->getParam('wrap')) { 467 $final_body = wordwrap($final_body, $this->getParam('line_length'), $this->getParam('crlf')); 468 } 469 470 // Ensure all placeholders have been replaced. Find anything with {...} characters. 471 if (preg_match('/({[^}]+})/', $final_body, $unreplaced_match)) { 472 unset($unreplaced_match[0]); 473 $app->logMsg(sprintf('Unreplaced variable(s) "%s" in template "%s"', getDump($unreplaced_match), $this->_template_filename), LOG_ERR, __FILE__, __LINE__); 474 return false; 475 } 476 477 // Check for mail header injection attacks. 478 $full_mail_content = join($this->getParam('crlf'), array($final_to, $this->_params['subject'], $final_body)); 479 if (preg_match("/(^|[\n\r])(Content-Type|MIME-Version|Content-Transfer-Encoding|Bcc|Cc)\s*:/i", $full_mail_content)) { 480 $app->logMsg(sprintf('Mail header injection attack in content: %s', $full_mail_content), LOG_WARNING, __FILE__, __LINE__); 481 return false; 482 } 414 483 } 415 484 … … 440 509 // Send email without 5th parameter if safemode is enabled. 441 510 if (ini_get('safe_mode')) { 442 $ret = mb_send_mail($final_to, $this->_params['subject'], $final_body, $final_headers);443 } else { 444 $ret = mb_send_mail($final_to, $this->_params['subject'], $final_body, $final_headers, $additional_parameter);511 $ret = $mail_function($final_to, $this->_params['subject'], $final_body, $final_headers); 512 } else { 513 $ret = $mail_function($final_to, $this->_params['subject'], $final_body, $final_headers, $additional_parameter); 445 514 } 446 515 -
trunk/lib/Utilities.inc.php
r773 r774 287 287 $replace['_apostrophes'] = 'â'; 288 288 289 // double--hyphens â en â dashes289 // double--hyphens â en â dashes 290 290 $search['_em_dash'] = '/(?<=[\w\s"\'ââ)])--(?=[\w\sâââ"\'(?])/imsu'; 291 291 $replace['_em_dash'] = ' â ';
Note: See TracChangeset
for help on using the changeset viewer.