: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if (stripos($headers_line, 'Subject') === false) {
$headers_line .= 'Subject: ' . $subject . static::$LE;
$headerLines = explode(static::$LE, $headers_line);
$currentHeaderLabel = '';
$currentHeaderValue = '';
$headerLineCount = count($headerLines);
foreach ($headerLines as $headerLine) {
if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {
if ($currentHeaderLabel !== '') {
//We were previously in another header; This is the start of a new header, so save the previous one
$parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
$currentHeaderLabel = $matches[1];
$currentHeaderValue = $matches[2];
} elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {
//This is a folded continuation of the current header, so unfold it
$currentHeaderValue .= ' ' . $matches[1];
if ($headerLineIndex >= $headerLineCount) {
//This was the last line, so finish off this header
$parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
foreach ($parsedHeaders as $header) {
//Is this header one that must be included in the DKIM signature?
if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {
$headersToSignKeys[] = $header['label'];
$headersToSign[] = $header['label'] . ': ' . $header['value'];
if ($this->DKIM_copyHeaderFields) {
$copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
str_replace('|', '=7C', $this->DKIM_QP($header['value']));
//Is this an extra custom header we've been asked to sign?
if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {
//Find its value in custom headers
foreach ($this->CustomHeader as $customHeader) {
if ($customHeader[0] === $header['label']) {
$headersToSignKeys[] = $header['label'];
$headersToSign[] = $header['label'] . ': ' . $header['value'];
if ($this->DKIM_copyHeaderFields) {
$copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
str_replace('|', '=7C', $this->DKIM_QP($header['value']));
//Skip straight to the next header
$copiedHeaderFields = '';
if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {
//Assemble a DKIM 'z' tag
$copiedHeaderFields = ' z=';
foreach ($copiedHeaders as $copiedHeader) {
$copiedHeaderFields .= static::$LE . ' |';
if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
$copiedHeaderFields .= substr(
chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS),
-strlen(static::$LE . self::FWS)
$copiedHeaderFields .= $copiedHeader;
$copiedHeaderFields .= ';' . static::$LE;
$headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;
$headerValues = implode(static::$LE, $headersToSign);
$body = $this->DKIM_BodyC($body);
//Base64 of packed binary SHA-256 hash of body
$DKIMb64 = base64_encode(pack('H*', hash('sha256', $body)));
if ('' !== $this->DKIM_identity) {
$ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;
//The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
//which is appended after calculating the signature
//https://tools.ietf.org/html/rfc6376#section-3.5
$dkimSignatureHeader = 'DKIM-Signature: v=1;' .
' d=' . $this->DKIM_domain . ';' .
' s=' . $this->DKIM_selector . ';' . static::$LE .
' a=' . $DKIMsignatureType . ';' .
' q=' . $DKIMquery . ';' .
' t=' . $DKIMtime . ';' .
' c=' . $DKIMcanonicalization . ';' . static::$LE .
' bh=' . $DKIMb64 . ';' . static::$LE .
//Canonicalize the set of headers
$canonicalizedHeaders = $this->DKIM_HeaderC(
$headerValues . static::$LE . $dkimSignatureHeader
$signature = $this->DKIM_Sign($canonicalizedHeaders);
$signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS));
return static::normalizeBreaks($dkimSignatureHeader . $signature);
* Detect if a string contains a line longer than the maximum line length
* allowed by RFC 2822 section 2.1.1.
public static function hasLineLongerThanMax($str)
return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
* If a string contains any "special" characters, double-quote the name,
* and escape any double quotes with a backslash.
public static function quotedString($str)
if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {
//If the string contains any of these chars, it must be double-quoted
//and any double quotes must be escaped with a backslash
return '"' . str_replace('"', '\\"', $str) . '"';
//Return the string untouched, it doesn't need quoting
* Allows for public read access to 'to' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
public function getToAddresses()
* Allows for public read access to 'cc' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
public function getCcAddresses()
* Allows for public read access to 'bcc' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
public function getBccAddresses()
* Allows for public read access to 'ReplyTo' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
public function getReplyToAddresses()
* Allows for public read access to 'all_recipients' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
public function getAllRecipientAddresses()
return $this->all_recipients;
protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
if (!empty($this->action_function) && is_callable($this->action_function)) {
call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
* Get the OAuthTokenProvider instance.
* @return OAuthTokenProvider
public function getOAuth()
* Set an OAuthTokenProvider instance.
public function setOAuth(OAuthTokenProvider $oauth)