Home > Back-end >  Magento 2 Laminas mail 'Invalid header value' error when email name contains German charac
Magento 2 Laminas mail 'Invalid header value' error when email name contains German charac

Time:01-25

When I try to send mail from admin order, if customer name has special German/Danish characters, email is not sending. Sending perfectly for other customers. The error I found was Invalid header value

I traced the error to file vendor/laminas/laminas-http/src/Header/HeaderValue.php

/**
     * Assert a header value is valid.
     *
     * @param string $value
     * @throws Exception\RuntimeException For invalid values.
     * @return void
     */
    public static function assertValid($value)
    {
        if (! self::isValid($value)) {
            throw new Exception\InvalidArgumentException('Invalid header value');
        }
    }

This function was getting called from vendor/laminas/laminas-mail/src/Header/AbstractAddressList.php

public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
    {
        $emails   = [];
        $encoding = $this->getEncoding();

        foreach ($this->getAddressList() as $address) {
            $email = $address->getEmail();
            $name  = $address->getName();

            // quote $name if value requires so
            if (! empty($name) && (false !== strpos($name, ',') || false !== strpos($name, ';'))) {
                // FIXME: what if name contains double quote?
                $name = sprintf('"%s"', $name);
            }

            if ($format === HeaderInterface::FORMAT_ENCODED
                && 'ASCII' !== $encoding
            ) {
                if (! empty($name)) {
                    $name = HeaderWrap::mimeEncodeValue($name, $encoding);
                }

                if (preg_match('/^(. )@([^@] )$/', $email, $matches)) {
                    $localPart = $matches[1];
                    $hostname  = $this->idnToAscii($matches[2]);
                    $email = sprintf('%s@%s', $localPart, $hostname);
                }
            }

            if (empty($name)) {
                $emails[] = $email;
            } else {
                $emails[] = sprintf('%s <%s>', $name, $email);
            }
        }

        // Ensure the values are valid before sending them.
        if ($format !== HeaderInterface::FORMAT_RAW) {
            foreach ($emails as $email) {
                HeaderValue::assertValid($email);
            }
        }

        return implode(',' . Headers::FOLDING, $emails);
    }

I have found a class Magento\Framework\Filter\RemoveAccents which replaces special characters with their usable counterparts, but as the function assertValid is a static function inside a final class, and function getFieldValue is inside an abstract class, I am unable to replace the characters.

CodePudding user response:

In these situations, (when the file you want to change is located inside vendor but outside of vendor/mgento,) you're better off looking further up the stack trace for another place to make your changes. For instance, you could create a Preference for \Magento\Framework\Mail\EmailMessage::convertAddressArrayToAddressList and scrub the data there. Or, you can go further up and create a plugin for \Magento\Framework\Mail\Message::addTo, etc.

CodePudding user response:

I got the solution by following Tyler's instruction to go back up the stack trace. Let me explain.

Class Laminas\Mail\Header\AbstractAddressList::getFieldValue uses the following code to get customer name and email

foreach ($this->getAddressList() as $address) {
        $email = $address->getEmail();
        $name  = $address->getName();

Here $this->getAddressList() comes from

/**
 * Get address list managed by this header
 *
 * @return AddressList
 */
public function getAddressList()
{
    if (null === $this->addressList) {
        $this->setAddressList(new AddressList());
    }
    return $this->addressList;
}

Where $this->addressList gets assigned on

/**
 * Set address list for this header
 *
 * @param  AddressList $addressList
 */
public function setAddressList(AddressList $addressList)
{
    $this->addressList = $addressList;
}

It uses Laminas\Mail\AddressList class. So going in there

/**
 * Create an address object
 *
 * @param  string $email
 * @param  string|null $name
 * @return Address
 */
protected function createAddress($email, $name)
{
    return new Address($email, $name);
}

is used to set name and email. Here it creates object of class Laminas\Mail\Address. So going in there, I found

/**
 * Retrieve name, if any
 *
 * @return null|string
 */
public function getName()
{
    return $this->name;
}

Modifying this return value was fixing my problem. So I created a plugin.

public function afterGetName(\Laminas\Mail\Address $subject,$result)
{
    $removeAccent = new \Magento\Framework\Filter\RemoveAccents(true);
    return $removeAccent->filter($result);
}
  • Related