From fe0e79336bf9015de6551b11ca5426f101a13d16 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 9 May 2019 09:23:25 +0200 Subject: [PATCH 01/41] updated version to 4.4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f1d2f09..78f0392 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } } } From 9a3ea56cf65febb4246caa15e0fd01639eb33ff3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 28 May 2019 17:41:12 +0200 Subject: [PATCH 02/41] Allow Symfony 5.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 78f0392..89c392f 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "egulias/email-validator": "^2.0", - "symfony/dependency-injection": "~3.4|^4.1" + "symfony/dependency-injection": "^3.4|^4.1|^5.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Mime\\": "" }, From 3efe6de6c36044b34f0fc9d2c53e0077c6a86121 Mon Sep 17 00:00:00 2001 From: Sebastiaan Stok Date: Thu, 23 May 2019 20:52:30 +0200 Subject: [PATCH 03/41] [Mime] Added SMimeSigner and Encryptor --- Crypto/SMime.php | 111 +++++++++++++++++++ Crypto/SMimeEncrypter.php | 58 ++++++++++ Crypto/SMimeSigner.php | 69 ++++++++++++ Part/SMimePart.php | 118 ++++++++++++++++++++ Tests/Crypto/SMimeEncryptorTest.php | 103 ++++++++++++++++++ Tests/Crypto/SMimeSignerTest.php | 163 ++++++++++++++++++++++++++++ Tests/Crypto/SMimeTestCase.php | 73 +++++++++++++ Tests/_data/ca.crt | 19 ++++ Tests/_data/ca.key | 27 +++++ Tests/_data/ca.srl | 1 + Tests/_data/create-cert.sh | 52 +++++++++ Tests/_data/encrypt.crt | 19 ++++ Tests/_data/encrypt.key | 27 +++++ Tests/_data/encrypt2.crt | 19 ++++ Tests/_data/encrypt2.key | 27 +++++ Tests/_data/intermediate.crt | 19 ++++ Tests/_data/intermediate.key | 27 +++++ Tests/_data/sign.crt | 20 ++++ Tests/_data/sign.key | 27 +++++ Tests/_data/sign2.crt | 19 ++++ Tests/_data/sign2.key | 27 +++++ Tests/_data/sign3.crt | 19 ++++ Tests/_data/sign3.key | 30 +++++ 23 files changed, 1074 insertions(+) create mode 100644 Crypto/SMime.php create mode 100644 Crypto/SMimeEncrypter.php create mode 100644 Crypto/SMimeSigner.php create mode 100644 Part/SMimePart.php create mode 100644 Tests/Crypto/SMimeEncryptorTest.php create mode 100644 Tests/Crypto/SMimeSignerTest.php create mode 100644 Tests/Crypto/SMimeTestCase.php create mode 100644 Tests/_data/ca.crt create mode 100644 Tests/_data/ca.key create mode 100644 Tests/_data/ca.srl create mode 100755 Tests/_data/create-cert.sh create mode 100644 Tests/_data/encrypt.crt create mode 100644 Tests/_data/encrypt.key create mode 100644 Tests/_data/encrypt2.crt create mode 100644 Tests/_data/encrypt2.key create mode 100644 Tests/_data/intermediate.crt create mode 100644 Tests/_data/intermediate.key create mode 100644 Tests/_data/sign.crt create mode 100644 Tests/_data/sign.key create mode 100644 Tests/_data/sign2.crt create mode 100644 Tests/_data/sign2.key create mode 100644 Tests/_data/sign3.crt create mode 100644 Tests/_data/sign3.key diff --git a/Crypto/SMime.php b/Crypto/SMime.php new file mode 100644 index 0000000..55941be --- /dev/null +++ b/Crypto/SMime.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Part\SMimePart; + +/** + * @author Sebastiaan Stok + * + * @internal + */ +abstract class SMime +{ + protected function normalizeFilePath(string $path): string + { + if (!file_exists($path)) { + throw new RuntimeException(sprintf('File does not exist: %s.', $path)); + } + + return 'file://'.str_replace('\\', '/', realpath($path)); + } + + protected function iteratorToFile(iterable $iterator, $stream): void + { + foreach ($iterator as $chunk) { + fwrite($stream, $chunk); + } + } + + protected function convertMessageToSMimePart($stream, string $type, string $subtype): SMimePart + { + rewind($stream); + + $headers = ''; + + while (!feof($stream)) { + $buffer = fread($stream, 78); + $headers .= $buffer; + + // Detect ending of header list + if (preg_match('/(\r\n\r\n|\n\n)/', $headers, $match)) { + $headersPosEnd = strpos($headers, $headerBodySeparator = $match[0]); + + break; + } + } + + $headers = $this->getMessageHeaders(trim(substr($headers, 0, $headersPosEnd))); + + fseek($stream, $headersPosEnd + \strlen($headerBodySeparator)); + + return new SMimePart($this->getStreamIterator($stream), $type, $subtype, $this->getParametersFromHeader($headers['content-type'])); + } + + protected function getStreamIterator($stream): iterable + { + while (!feof($stream)) { + yield fread($stream, 16372); + } + } + + private function getMessageHeaders(string $headerData): array + { + $headers = []; + $headerLines = explode("\r\n", str_replace("\n", "\r\n", str_replace("\r\n", "\n", $headerData))); + $currentHeaderName = ''; + + // Transform header lines into an associative array + foreach ($headerLines as $headerLine) { + // Empty lines between headers indicate a new mime-entity + if ('' === $headerLine) { + break; + } + + // Handle headers that span multiple lines + if (false === strpos($headerLine, ':')) { + $headers[$currentHeaderName] .= ' '.trim($headerLine); + continue; + } + + $header = explode(':', $headerLine, 2); + $currentHeaderName = strtolower($header[0]); + $headers[$currentHeaderName] = trim($header[1]); + } + + return $headers; + } + + private function getParametersFromHeader(string $header): array + { + $params = []; + + preg_match_all('/(?P[a-z-0-9]+)=(?P"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches); + + foreach ($matches['value'] as $pos => $paramValue) { + $params[$matches['name'][$pos]] = trim($paramValue, '"'); + } + + return $params; + } +} diff --git a/Crypto/SMimeEncrypter.php b/Crypto/SMimeEncrypter.php new file mode 100644 index 0000000..72f59ce --- /dev/null +++ b/Crypto/SMimeEncrypter.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Message; + +/** + * @author Sebastiaan Stok + */ +final class SMimeEncrypter extends SMime +{ + private $certs; + private $cipher; + + /** + * @param string|string[] $certificate Either a lone X.509 certificate, or an array of X.509 certificates + */ + public function __construct($certificate, int $cipher = OPENSSL_CIPHER_AES_256_CBC) + { + if (\is_array($certificate)) { + $this->certs = array_map([$this, 'normalizeFilePath'], $certificate); + } else { + $this->certs = $this->normalizeFilePath($certificate); + } + + $this->cipher = $cipher; + } + + public function encrypt(Message $message): Message + { + $bufferFile = tmpfile(); + $outputFile = tmpfile(); + + $this->iteratorToFile($message->toIterable(), $bufferFile); + + if (!@openssl_pkcs7_encrypt(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->certs, [], 0, $this->cipher)) { + throw new RuntimeException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string())); + } + + $mimePart = $this->convertMessageToSMimePart($outputFile, 'application', 'pkcs7-mime'); + $mimePart->getHeaders() + ->addTextHeader('Content-Transfer-Encoding', 'base64') + ->addParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'smime.p7m']) + ; + + return new Message($message->getHeaders(), $mimePart); + } +} diff --git a/Crypto/SMimeSigner.php b/Crypto/SMimeSigner.php new file mode 100644 index 0000000..71ce5df --- /dev/null +++ b/Crypto/SMimeSigner.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Crypto; + +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Message; + +/** + * @author Sebastiaan Stok + */ +final class SMimeSigner extends SMime +{ + private $signCertificate; + private $signPrivateKey; + private $signOptions; + private $extraCerts; + + /** + * @var string|null + */ + private $privateKeyPassphrase; + + /** + * @see https://secure.php.net/manual/en/openssl.pkcs7.flags.php + * + * @param string $certificate + * @param string $privateKey A file containing the private key (in PEM format) + * @param string|null $privateKeyPassphrase A passphrase of the private key (if any) + * @param string $extraCerts A file containing intermediate certificates (in PEM format) needed by the signing certificate + * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() + */ + public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, int $signOptions = PKCS7_DETACHED) + { + $this->signCertificate = $this->normalizeFilePath($certificate); + + if (null !== $privateKeyPassphrase) { + $this->signPrivateKey = [$this->normalizeFilePath($privateKey), $privateKeyPassphrase]; + } else { + $this->signPrivateKey = $this->normalizeFilePath($privateKey); + } + + $this->signOptions = $signOptions; + $this->extraCerts = $extraCerts ? realpath($extraCerts) : null; + $this->privateKeyPassphrase = $privateKeyPassphrase; + } + + public function sign(Message $message): Message + { + $bufferFile = tmpfile(); + $outputFile = tmpfile(); + + $this->iteratorToFile($message->getBody()->toIterable(), $bufferFile); + + if (!@openssl_pkcs7_sign(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->signCertificate, $this->signPrivateKey, [], $this->signOptions, $this->extraCerts)) { + throw new RuntimeException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string())); + } + + return new Message($message->getHeaders(), $this->convertMessageToSMimePart($outputFile, 'multipart', 'signed')); + } +} diff --git a/Part/SMimePart.php b/Part/SMimePart.php new file mode 100644 index 0000000..59142c3 --- /dev/null +++ b/Part/SMimePart.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Part; + +use Symfony\Component\Mime\Header\Headers; + +/** + * @author Sebastiaan Stok + * + * @experimental in 4.4 + */ +class SMimePart extends AbstractPart +{ + private $body; + private $type; + private $subtype; + private $parameters; + + /** + * @param iterable|string $body + */ + public function __construct($body, string $type, string $subtype, array $parameters) + { + parent::__construct(); + + if (!\is_string($body) && !is_iterable($body)) { + throw new \TypeError(sprintf('The body of "%s" must be a string or a iterable (got "%s").', self::class, \is_object($body) ? \get_class($body) : \gettype($body))); + } + + $this->body = $body; + $this->type = $type; + $this->subtype = $subtype; + $this->parameters = $parameters; + } + + public function getMediaType(): string + { + return $this->type; + } + + public function getMediaSubtype(): string + { + return $this->subtype; + } + + public function bodyToString(): string + { + if (\is_string($this->body)) { + return $this->body; + } + + $body = ''; + foreach ($this->body as $chunk) { + $body .= $chunk; + } + $this->body = $body; + + return $body; + } + + public function bodyToIterable(): iterable + { + if (\is_string($this->body)) { + yield $this->body; + + return; + } + + $body = ''; + foreach ($this->body as $chunk) { + $body .= $chunk; + yield $chunk; + } + $this->body = $body; + } + + public function getPreparedHeaders(): Headers + { + $headers = clone parent::getHeaders(); + + $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype()); + + foreach ($this->parameters as $name => $value) { + $headers->setHeaderParameter('Content-Type', $name, $value); + } + + return $headers; + } + + public function __sleep(): array + { + // convert iterables to strings for serialization + if (is_iterable($this->body)) { + $this->body = $this->bodyToString(); + } + + $this->_headers = $this->getHeaders(); + + return ['_headers', 'body', 'type', 'subtype', 'parameters']; + } + + public function __wakeup(): void + { + $r = new \ReflectionProperty(AbstractPart::class, 'headers'); + $r->setAccessible(true); + $r->setValue($this, $this->_headers); + unset($this->_headers); + } +} diff --git a/Tests/Crypto/SMimeEncryptorTest.php b/Tests/Crypto/SMimeEncryptorTest.php new file mode 100644 index 0000000..a05b1e8 --- /dev/null +++ b/Tests/Crypto/SMimeEncryptorTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Tests\Crypto; + +use Symfony\Component\Mime\Crypto\SMimeEncrypter; +use Symfony\Component\Mime\Crypto\SMimeSigner; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Message; + +class SMimeEncryptorTest extends SMimeTestCase +{ + public function testEncryptMessage() + { + $message = (new Email()) + ->date(new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->to('fabien@symfony.com') + ->subject('Testing') + ->from('noreply@example.com') + ->text('El Barto was not here'); + + $message->getHeaders()->addIdHeader('Message-ID', 'some@id'); + + $encrypter = new SMimeEncrypter($this->samplesDir.'encrypt.crt'); + $encryptedMessage = $encrypter->encrypt($message); + + $this->assertMessageIsEncryptedProperly($encryptedMessage, $message); + } + + public function testEncryptSignedMessage() + { + $message = (new Email()) + ->date(new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->to('fabien@symfony.com') + ->bcc('luna@symfony.com') + ->subject('Testing') + ->from('noreply@example.com') + ->text('El Barto was not here'); + + $message->getHeaders()->addIdHeader('Message-ID', 'some@id'); + + $signer = new SMimeSigner($this->samplesDir.'sign.crt', $this->samplesDir.'sign.key'); + $signedMessage = $signer->sign($message); + + $encrypter = new SMimeEncrypter($this->samplesDir.'encrypt.crt'); + $encryptedMessage = $encrypter->encrypt($signedMessage); + + $this->assertMessageIsEncryptedProperly($encryptedMessage, $signedMessage); + } + + public function testEncryptMessageWithMultipleCerts() + { + $message = (new Email()) + ->date(new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->to('fabien@symfony.com') + ->subject('Testing') + ->from('noreply@example.com') + ->text('El Barto was not here'); + + $message2 = (new Email()) + ->date(new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->to('luna@symfony.com') + ->subject('Testing') + ->from('noreply@example.com') + ->text('El Barto was not here'); + + $message->getHeaders()->addIdHeader('Message-ID', 'some@id'); + $message2->getHeaders()->addIdHeader('Message-ID', 'some@id2'); + + $encrypter = new SMimeEncrypter(['fabien@symfony.com' => $this->samplesDir.'encrypt.crt', 'luna@symfony.com' => $this->samplesDir.'encrypt2.crt']); + + $this->assertMessageIsEncryptedProperly($encrypter->encrypt($message), $message); + $this->assertMessageIsEncryptedProperly($encrypter->encrypt($message2), $message2); + } + + private function assertMessageIsEncryptedProperly(Message $message, Message $originalMessage): void + { + $messageFile = $this->generateTmpFilename(); + file_put_contents($messageFile, $message->toString()); + + $outputFile = $this->generateTmpFilename(); + + $this->assertMessageHeaders($message, $originalMessage); + $this->assertTrue( + openssl_pkcs7_decrypt( + $messageFile, + $outputFile, + 'file://'.$this->samplesDir.'encrypt.crt', + 'file://'.$this->samplesDir.'encrypt.key' + ), + sprintf('Decryption of the message failed. Internal error "%s".', openssl_error_string()) + ); + $this->assertEquals(str_replace("\r", '', $originalMessage->toString()), str_replace("\r", '', file_get_contents($outputFile))); + } +} diff --git a/Tests/Crypto/SMimeSignerTest.php b/Tests/Crypto/SMimeSignerTest.php new file mode 100644 index 0000000..ab645b3 --- /dev/null +++ b/Tests/Crypto/SMimeSignerTest.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Tests\Crypto; + +use Symfony\Component\Mime\Crypto\SMimeEncrypter; +use Symfony\Component\Mime\Crypto\SMimeSigner; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\Part\TextPart; + +class SMimeSignerTest extends SMimeTestCase +{ + public function testSignedMessage() + { + $message = new Message( + (new Headers()) + ->addDateHeader('Date', new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->addMailboxListHeader('From', ['fabien@symfony.com']), + new TextPart('content') + ); + + $signer = new SMimeSigner($this->samplesDir.'sign.crt', $this->samplesDir.'sign.key'); + $signedMessage = $signer->sign($message); + + $this->assertMessageSignatureIsValid($signedMessage, $message); + } + + public function testSignEncryptedMessage() + { + $message = (new Email()) + ->date(new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->to('fabien@symfony.com') + ->subject('Testing') + ->from('noreply@example.com') + ->text('El Barto was not here'); + + $message->getHeaders()->addIdHeader('Message-ID', 'some@id'); + + $encrypter = new SMimeEncrypter($this->samplesDir.'encrypt.crt'); + $encryptedMessage = $encrypter->encrypt($message); + + $signer = new SMimeSigner($this->samplesDir.'sign.crt', $this->samplesDir.'sign.key'); + $signedMessage = $signer->sign($encryptedMessage); + + $this->assertMessageSignatureIsValid($signedMessage, $message); + } + + public function testSignedMessageWithPassphrase() + { + $message = new Message( + (new Headers()) + ->addDateHeader('Date', new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->addMailboxListHeader('From', ['fabien@symfony.com']), + new TextPart('content') + ); + + $signer = new SMimeSigner($this->samplesDir.'sign3.crt', $this->samplesDir.'sign3.key', 'symfony-rocks'); + $signedMessage = $signer->sign($message); + + $this->assertMessageSignatureIsValid($signedMessage, $message); + } + + public function testProperSerialiable() + { + $message = (new Email()) + ->date(new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->to('fabien@symfony.com') + ->subject('Testing') + ->from('noreply@example.com') + ->text('El Barto was not here'); + + $message->getHeaders()->addIdHeader('Message-ID', 'some@id'); + + $signer = new SMimeSigner($this->samplesDir.'sign.crt', $this->samplesDir.'sign.key'); + $signedMessage = $signer->sign($message); + + $restoredMessage = unserialize(serialize($signedMessage)); + + self::assertSame($this->iterableToString($signedMessage->toIterable()), $this->iterableToString($restoredMessage->toIterable())); + self::assertSame($signedMessage->toString(), $restoredMessage->toString()); + + $this->assertMessageSignatureIsValid($restoredMessage, $message); + } + + public function testSignedMessageWithBcc() + { + $message = (new Email()) + ->date(new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->addBcc('fabien@symfony.com', 's.stok@rollerscapes.net') + ->subject('I am your sign of fear') + ->from('noreply@example.com') + ->text('El Barto was not here'); + + $signer = new SMimeSigner($this->samplesDir.'sign.crt', $this->samplesDir.'sign.key'); + $signedMessage = $signer->sign($message); + + $this->assertMessageSignatureIsValid($signedMessage, $message); + } + + public function testSignedMessageWithAttachments() + { + $message = new Email((new Headers()) + ->addDateHeader('Date', new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->addMailboxListHeader('From', ['fabien@symfony.com']) + ); + $message->html($content = 'html content '); + $message->text('text content'); + $message->attach(fopen(__DIR__.'/../Fixtures/mimetypes/test', 'r')); + $message->attach(fopen(__DIR__.'/../Fixtures/mimetypes/test.gif', 'r'), 'test.gif'); + + $signer = new SMimeSigner($this->samplesDir.'sign.crt', $this->samplesDir.'sign.key'); + + $signedMessage = $signer->sign($message); + $this->assertMessageSignatureIsValid($signedMessage, $message); + } + + public function testSignedMessageExtraCerts() + { + $message = new Message( + (new Headers()) + ->addDateHeader('Date', new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->addMailboxListHeader('From', ['fabien@symfony.com']), + new TextPart('content') + ); + + $signer = new SMimeSigner( + $this->samplesDir.'sign.crt', + $this->samplesDir.'sign.key', + null, + $this->samplesDir.'intermediate.crt', + PKCS7_DETACHED + ); + $signedMessage = $signer->sign($message); + + $this->assertMessageSignatureIsValid($signedMessage, $message); + } + + private function assertMessageSignatureIsValid(Message $message, Message $originalMessage): void + { + $messageFile = $this->generateTmpFilename(); + $messageString = $message->toString(); + file_put_contents($messageFile, $messageString); + + $this->assertMessageHeaders($message, $originalMessage); + $this->assertTrue(openssl_pkcs7_verify($messageFile, 0, $this->generateTmpFilename(), [$this->samplesDir.'ca.crt']), sprintf('Verification of the message %s failed. Internal error "%s".', $messageFile, openssl_error_string())); + + if (false === strpos($messageString, 'enveloped-data')) { + // Tamper to ensure it actually verified + file_put_contents($messageFile, str_replace('Content-Transfer-Encoding: ', 'Content-Transfer-Encoding: ', $messageString)); + $this->assertFalse(openssl_pkcs7_verify($messageFile, 0, $this->generateTmpFilename(), [$this->samplesDir.'ca.crt']), sprintf('Verification of the message failed. Internal error "%s".', openssl_error_string())); + } + } +} diff --git a/Tests/Crypto/SMimeTestCase.php b/Tests/Crypto/SMimeTestCase.php new file mode 100644 index 0000000..f562f18 --- /dev/null +++ b/Tests/Crypto/SMimeTestCase.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Tests\Crypto; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mime\Exception\RuntimeException; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; + +abstract class SMimeTestCase extends TestCase +{ + protected $samplesDir; + + protected function setUp(): void + { + $this->samplesDir = str_replace('\\', '/', realpath(__DIR__.'/../').'/_data/'); + } + + protected function generateTmpFilename(): string + { + return stream_get_meta_data(tmpfile())['uri']; + } + + protected function normalizeFilePath(string $path): string + { + if (!file_exists($path)) { + throw new RuntimeException(sprintf('File does not exist: %s', $path)); + } + + return str_replace('\\', '/', realpath($path)); + } + + protected function iterableToString(iterable $iterable): string + { + $string = ''; + + // Can't use iterator_to_array as the generators are merged internally, + // leading to overwritten keys + foreach ($iterable as $chunk) { + $string .= $chunk; + } + + return $string; + } + + protected function assertMessageHeaders(Message $message, RawMessage $originalMessage): void + { + $messageString = $message->toString(); + self::assertNotContains('Bcc: ', $messageString, '', true); + + if (!$originalMessage instanceof Message) { + return; + } + + if ($originalMessage->getHeaders()->has('Bcc')) { + self::assertEquals($originalMessage->getHeaders()->get('Bcc'), $message->getHeaders()->get('Bcc')); + } + + if ($originalMessage->getHeaders()->has('Subject')) { + self::assertEquals($originalMessage->getHeaders()->get('Subject'), $message->getPreparedHeaders()->get('Subject')); + self::assertContains('Subject:', $messageString, '', true); + } + } +} diff --git a/Tests/_data/ca.crt b/Tests/_data/ca.crt new file mode 100644 index 0000000..bca02b3 --- /dev/null +++ b/Tests/_data/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFDCCAfwCCQDaMw8tuy1dgDANBgkqhkiG9w0BAQsFADBMMRcwFQYDVQQDDA5T +eW1mb255TWltZSBDQTEUMBIGA1UECgwLU3ltZm9ueU1pbWUxDjAMBgNVBAcMBVBh +cmlzMQswCQYDVQQGEwJGUjAeFw0xOTA0MTkxNDIwMTFaFw0yMzA0MTgxNDIwMTFa +MEwxFzAVBgNVBAMMDlN5bWZvbnlNaW1lIENBMRQwEgYDVQQKDAtTeW1mb255TWlt +ZTEOMAwGA1UEBwwFUGFyaXMxCzAJBgNVBAYTAkZSMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAnvxOWE8qOVkuYbTu6u4Oao2n91FPF6umrcF8mq0uD2G0 +dtOJuFaR7FeElmJnHfWvqvesCigXyA7kpdVBFGhEo83SGYTbPSGzehWDc7Kvc321 +UPvNb61T2Ekdo+5ufrpbzlOPtTTaVL98dFEZntYNM3CXnnSSdeKz38NlHHV3QsDZ +crQRMxHrYi2bgkhxVoAY03ZQRbb95rEE1cfyGZ0x6VSBrVC2nnEUT2vopwny/vy+ +QSn3oga+ucMkxJdoD8MA13Zh5I4Uiozl82xoWH/zmVrqrrO2lNBv7WYOnwbv6MSr +5kCE3Kcqzs8qAGv62GYyS4exIMEZsbbPv3cvp9hgYQIDAQABMA0GCSqGSIb3DQEB +CwUAA4IBAQBuJtPqAX6ApOymDux9sRqxx5FMIIEX2TmanSSSLesP0AVVLv8Am8/p +Xs8N9e49KoQhnQ3FmdtwY6IV6f3yIMnZxmkXZoUi4zCkSZd/+2iap1c51zV1b6NC +4C5LZtdWzhons4jOmtmxaMSy08oPPYv1wXATjjfHvqqYa/7axLY1mqbxLYC437Fv +H5zkdzQM2qXpIgtCjlXfOd/L9Az5DTSH4UvWiiocRdmnxGP+nMEOuUUvLzokJSeq +Otw4gjxczF8NQ/g/io6iG3w4OfjgRrCpuMv/l3eYClC7vDXOX9S172CpzaD/qkHM +NFxckxTgT4ylmivmHZWym4xS1bkAAAsd +-----END CERTIFICATE----- diff --git a/Tests/_data/ca.key b/Tests/_data/ca.key new file mode 100644 index 0000000..4832a1d --- /dev/null +++ b/Tests/_data/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAnvxOWE8qOVkuYbTu6u4Oao2n91FPF6umrcF8mq0uD2G0dtOJ +uFaR7FeElmJnHfWvqvesCigXyA7kpdVBFGhEo83SGYTbPSGzehWDc7Kvc321UPvN +b61T2Ekdo+5ufrpbzlOPtTTaVL98dFEZntYNM3CXnnSSdeKz38NlHHV3QsDZcrQR +MxHrYi2bgkhxVoAY03ZQRbb95rEE1cfyGZ0x6VSBrVC2nnEUT2vopwny/vy+QSn3 +oga+ucMkxJdoD8MA13Zh5I4Uiozl82xoWH/zmVrqrrO2lNBv7WYOnwbv6MSr5kCE +3Kcqzs8qAGv62GYyS4exIMEZsbbPv3cvp9hgYQIDAQABAoIBAD/Y1WGzkSJsxSqp +7dTc+18hOlYhCiFYZtyaun6nk7rLoxyhQUqNQZbnYrC+HekzNHP1eNqvVTWbfYl3 +heY7JW2fB4QGDcGUGi6qGxtIpBs+XaWDKfJyahyO6F9gLnGoR5wphKnh6thj+ggA +Vciq76w7yDfzWqoK+++d2ao/JkDg7YrpOQonfceYgjTtiXXFDV4cm4GgKr7gWolt +AqZbHcbH6pmbwRduT+g5QjsYmYPven/ji6Mr2eTFwE0qjlwj4LHlEWuKgpwAnLc/ +jzdnx3UjRGTbiMnbxrv4sHApW9Bb02aRWHVG3axkxWFyWefKuGRvXUZAguGvMpeq +Ng6Jc9ECgYEA0YpHxa32IFRzkOC7Vs79uKWmkbiYZihSrAyCG7Xo5rxTtB5HUcB+ +qIrFU2t2OSDffrRS5C6Ewpw3kBgYElsoYyqL/h1Kb+SQzZVwgK3PAF9p4mcgzyCU +Q25Nqy2CyX3gZblQMK6ui5aI7ZC3WE2wl8fxAneZOtHEEw2e0DaiUP0CgYEAwjx+ +gQr+NHFbDSfhh1IdIz+kGBgR+TS0OIjE2/Mb5IUfDzMsWGo0JEpTH1ma+e7VrxCC +9o47dvz5PXlHAuxsgLEXN7NEPqhiluAbTG/YEpsYeqftqKJsFROmFa3TDeEp3LGz +2OVY/uZjxNVVfljS/weGhOXGfATwQQoAUFbEzDUCgYEAznRsmvz4EIqlAw4qBzIT +EydDozg6EA2Sxynb1+m3+/96iXF727TKFs4D9llfNpKJIpIRSfn7nLPGmxbiQNPI +S0zUeh/qA600bxraqi6WUkuwS/5IeUwkSPwZUpuYzWZU/mVD+XNjTu2XJFr+Cuch +I6tAb6nfM/ESO6Oj4oqyCxECgYBsXr4iF1UPQ3OOmoKtMnZJVVejjcJxbSNkK4LS +SQh17oQOwflq9w5SdRl9c0wRSFz2iNrY3zB0Sd5xmvmwuuIqxyNyE1XvM5mWHkF8 +2yYN83Sr8oeZv81X0ReoHsyTgN4PYSI70HJf/YEKsBA8JyjJ25QFEAI27bZyQzc7 +m72/RQKBgEtyibh8X7DC9B3oVMZAX1BJJDzDSH1RyRaoa+7nARSl90qJD877NZ8o +jteoRFNJJzruADouffK+lTlMtwdfQJQW4wGYGiyr1S5dKXNsPmcnKCj7HbvBphVA +oCzZi3txFcOmH4IZ5HA0VxvGViQwV7fyl5ch7XVqSFOeFaa6lIF5 +-----END RSA PRIVATE KEY----- diff --git a/Tests/_data/ca.srl b/Tests/_data/ca.srl new file mode 100644 index 0000000..8543646 --- /dev/null +++ b/Tests/_data/ca.srl @@ -0,0 +1 @@ +C51E36445BB0C79B diff --git a/Tests/_data/create-cert.sh b/Tests/_data/create-cert.sh new file mode 100755 index 0000000..3f36d2f --- /dev/null +++ b/Tests/_data/create-cert.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -days 1460 -subj '/CN=SymfonyMime CA/O=SymfonyMime/L=Paris/C=FR' -out ca.crt +openssl x509 -in ca.crt -clrtrust -out ca.crt + +## Sign + +openssl genrsa -out sign.key 2048 +openssl req -new -key sign.key -subj '/CN=fabien@symfony.com/O=SymfonyMime/L=Paris/C=FR/emailAddress=fabien@symfony.com' -out sign.csr +openssl x509 -req -in sign.csr -CA ca.crt -CAkey ca.key -out sign.crt -days 1460 -addtrust emailProtection +openssl x509 -in sign.crt -clrtrust -out sign.crt + +rm sign.csr + +openssl genrsa -out intermediate.key 2048 +openssl req -new -key intermediate.key -subj '/CN=SymfonyMime Intermediate/O=SymfonyMime/L=Paris/C=FR' -out intermediate.csr +openssl x509 -req -in intermediate.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out intermediate.crt -days 1460 +openssl x509 -in intermediate.crt -clrtrust -out intermediate.crt + +rm intermediate.csr + +openssl genrsa -out sign2.key 2048 +openssl req -new -key sign2.key -subj '/CN=SymfonyMime-User2/O=SymfonyMime/L=Paris/C=FR' -out sign2.csr +openssl x509 -req -in sign2.csr -CA intermediate.crt -CAkey intermediate.key -set_serial 01 -out sign2.crt -days 1460 -addtrust emailProtection +openssl x509 -in sign2.crt -clrtrust -out sign2.crt + +rm sign2.csr + +### Sign with passphrase +openssl genrsa -aes256 -passout pass:symfony-rocks -out sign3.key 2048 +openssl req -new -key sign3.key -passin pass:symfony-rocks -subj '/CN=SymfonyMime-User3/O=SymfonyMime/L=Paris/C=FR' -out sign3.csr +openssl x509 -req -in sign3.csr -CA ca.crt -CAkey ca.key -out sign3.crt -days 1460 -addtrust emailProtection +openssl x509 -in sign3.crt -clrtrust -out sign3.crt + +rm sign3.csr + +## Encrypt + +openssl genrsa -out encrypt.key 2048 +openssl req -new -key encrypt.key -subj '/CN=SymfonyMime-User/O=SymfonyMime/L=Paris/C=FR' -out encrypt.csr +openssl x509 -req -in encrypt.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out encrypt.crt -days 1460 -addtrust emailProtection +openssl x509 -in encrypt.crt -clrtrust -out encrypt.crt + +rm encrypt.csr + +openssl genrsa -out encrypt2.key 2048 +openssl req -new -key encrypt2.key -subj '/CN=SymfonyMime-User2/O=SymfonyMime/L=Paris/C=FR' -out encrypt2.csr +openssl x509 -req -in encrypt2.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out encrypt2.crt -days 1460 -addtrust emailProtection +openssl x509 -in encrypt2.crt -clrtrust -out encrypt2.crt + +rm encrypt2.csr diff --git a/Tests/_data/encrypt.crt b/Tests/_data/encrypt.crt new file mode 100644 index 0000000..e8a5a7c --- /dev/null +++ b/Tests/_data/encrypt.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFjCCAf4CCQDFHjZEW7DHmjANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T +eW1mb255TWltZSBDQTEUMBIGA1UECgwLU3ltZm9ueU1pbWUxDjAMBgNVBAcMBVBh +cmlzMQswCQYDVQQGEwJGUjAeFw0xOTA0MTkxNDIwMTdaFw0yMzA0MTgxNDIwMTda +ME4xGTAXBgNVBAMMEFN5bWZvbnlNaW1lLVVzZXIxFDASBgNVBAoMC1N5bWZvbnlN +aW1lMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCxnMT1TGmWhBp4K6IKztiplKVsdoYvi8JsflTpBHiw +/tLB3ikytItSADuqb/aEX/upgpvPQNJWa0Gf7f9yOQ0CekhTsNtP+o7UA9LtGrcI +lM1szBoaVhjpBgBAyP5OXcK7pOSRmUgp+vD/I9TRdRdzwwoJzvb35gpWGNZJ3WF0 +k9z4KqjdJDpQ7QBcEwZXVr8z5VnQ3gl8olY0AyN9Dh6B52uejGd1fBHf5v+hAR+5 +A0AAOOsTCa4kSXU2KaX9fNd0z/oK+GowfYtfrcCCVLaA6rmEGATQ9meGb54VBFVY +xarMX0ZY+0C3r8a9h8dJ9qxisMWksKLW8mE97/CclNHlAgMBAAEwDQYJKoZIhvcN +AQEFBQADggEBAAP4r76F+5EF+wgOvDlDU+KYXI4LfAy/yIvI5cDOLh65iAwgSWKX +HQPBDzPbQoJaTwj4XPwc4Ygrk7yftgcdYXRm5GWs5pp7DvSfskaX7TSuvNHt0M2A +gAo/rPH5BXp0/C+zgcmFVL067uhB10YHgsrX1ppLFPOsWvXNGAsKA4Qt2pxquI/g +UpNoucZ45Y1+idUq99jQr7sXdL3o5o1LLUdI64vrV/y2AYhUGn+NJvz1bXsp5NIV +jfBaYrAdZ4BMOF6gDMaJekI4PMcoH9sJFr1OIcKnk+UlGir+gAuaQGjKjOKjhB2r +KpZ7PMSJTC+bJYl3KVoIjBJ9/Bf1yjygb38= +-----END CERTIFICATE----- diff --git a/Tests/_data/encrypt.key b/Tests/_data/encrypt.key new file mode 100644 index 0000000..b7d1e91 --- /dev/null +++ b/Tests/_data/encrypt.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsZzE9UxploQaeCuiCs7YqZSlbHaGL4vCbH5U6QR4sP7Swd4p +MrSLUgA7qm/2hF/7qYKbz0DSVmtBn+3/cjkNAnpIU7DbT/qO1APS7Rq3CJTNbMwa +GlYY6QYAQMj+Tl3Cu6TkkZlIKfrw/yPU0XUXc8MKCc729+YKVhjWSd1hdJPc+Cqo +3SQ6UO0AXBMGV1a/M+VZ0N4JfKJWNAMjfQ4egedrnoxndXwR3+b/oQEfuQNAADjr +EwmuJEl1Niml/XzXdM/6CvhqMH2LX63AglS2gOq5hBgE0PZnhm+eFQRVWMWqzF9G +WPtAt6/GvYfHSfasYrDFpLCi1vJhPe/wnJTR5QIDAQABAoIBADhp4uVG8AKu0vl4 +Ym+sY4T5gdGBk/1mFsr/FVkt4mfViHurZMqGLfpNuKXaCiLhmb2tjm+11xk72AxE +O+670DYJQQ/UDNTKcLNGw6gr5BcFrHnyGhhjYGYjUdFCBgQ+I6wWI8NbPGCZJBLl +/qLI3joWqQmUgz0aBA50tRuhBWNRS9lNDfoPpibzFNjkxMzb3X3KdbsTfpH7Ocj/ +bOBuS1mnsm/xh30RzN0w/2yIzpxX4XvGy5eftMLWZY22NkbnDgGbGHDvNR3mjKkZ +8QF4Urx86VnfTnA6f0m/QS3YXEWk7RxUEGjzwIRv+6FcY+mFEsehWnly2KP3TG3S +65Z2SgECgYEA6Fe2NdjRbzBCukGa4/fZyrCggQq6Pr448QcyQEenf/X9CulroPtH +OiiIDuU6mCOBQVHp1FiCtZQT9hTfMszrhy7AMtJQncmQkMcbslEd8JgvDj4Jw64u +HcnKupNxfVew+at2u+GA4w88ntXxrcNl8Mde7aPnytAyUWPGsbKcCpkCgYEAw7Jy +yR2KFW0YhIePL1cEzA5J62Yy0yM8MJXHXshy3v1qU9LKsdVk7fbB9UojEnEGcu2R +T2sW2wQqxIo442KUmStisTtQ8pyAOyQyfzIVRSlHTh4BKlDp8SMuGdnOibavttHK +q/RgeOiXXG0Yfpf3sKDHSmQv7TGlsI07O6Z0vS0CgYEAgbc+jk+PlgEer/girrXY +jTYRVhoUIyV2ivKWlpaqqGFAtg/dvBGuEYVBePd3wCrKZhqCbsA/sXqLrm62shkA +QgfS3EzZH07CfGH9T4/EJGgClXQDZZFgQ9c+bO4WhYEo2CtnbbuXhq0iDheqB3Y4 ++rWEhS5mIbAc952598mdHrkCgYARF5jm7+mLjYfCq4RaAiOtHuJd6QMvZbhwFeTf +5moCB+gtgg+qEJVMI201W1BM4ApMJ2u1oAjTAD4sBFaLpaSM7DkmeaPMTNb2U2cF +rP4mmEBeFkjLxV1pbkUshNWBOa+HLDOjaSiz5ryxmeW1yNgdWS2O1clJ0jhCf1NZ +FmTD0QKBgGYjCX6vZl+aEchm3Ie18Vp8cNUu9CYAiDiDzEwgxgiTfRCIJywWjv5v +ll4lqtMgsrDrmI8fBFq4BKytMFvgPqW0sI7U4Fu1vArFeyTwCgfR8VMO7L+qvbWE +MKKKTeO8aTjTiNJ3b/eIkFDAKU+yQOhVR3VqbduqEeVtxRgzMOIh +-----END RSA PRIVATE KEY----- diff --git a/Tests/_data/encrypt2.crt b/Tests/_data/encrypt2.crt new file mode 100644 index 0000000..d3ba774 --- /dev/null +++ b/Tests/_data/encrypt2.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFzCCAf8CCQDFHjZEW7DHmzANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T +eW1mb255TWltZSBDQTEUMBIGA1UECgwLU3ltZm9ueU1pbWUxDjAMBgNVBAcMBVBh +cmlzMQswCQYDVQQGEwJGUjAeFw0xOTA0MTkxNDIwMjFaFw0yMzA0MTgxNDIwMjFa +ME8xGjAYBgNVBAMMEVN5bWZvbnlNaW1lLVVzZXIyMRQwEgYDVQQKDAtTeW1mb255 +TWltZTEOMAwGA1UEBwwFUGFyaXMxCzAJBgNVBAYTAkZSMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAvGX70bC9IIjPKIGN3FKR3wNHD5UdXhgEWpMDuQeA +gZ01LRc+tTactRMsuI3lTXCGOmU+kXpT03GcUEB4sP4ykSw04umDn8UbZ0o9WfzW +8c2Es3iYY/sDr4f7KUMaGqrARZrA9mJM4jvT49lOVWoiyzZ2Jgx2gDtFyCEW1b+0 +Hqnx4zjhBlCfe6XLpGgEtMwZ9tcmV96BBmlNVNHJbjiSqrsE97FTxxXzQgAmYDRc +qVAZicNcoNlDo/nV9A0n5ygA2Mgx6LF0HUAjf9YRXvRQ4BARtDJV9q/dzu5zxolS +mZOWxdlaCkTbeITGmRJNRl6BJiQu5kFRmzTO/Egt6bd5DwIDAQABMA0GCSqGSIb3 +DQEBBQUAA4IBAQAO6gTF27+s2CaCFE9VOHsqr/+9Rj3jYXefPD1NR4VU7fARXOGA +dgXW4PhNs2yfgBG2YJwK0uHRsLLwosh6KXZeyBm5XGT8QnzGVj/pZFJKuY0iIK9y +v4liJkLRKfUNPNEW214c3wcgd7chSOM6eV8rJFtnNyju4LnfnnNGFT2w48rccAyU +ZsL3BsQ40b/RUqBB12rNoKRyzmLVhdkTU/gTPYAVz9VQqtGXmYrqYQNuyenOYWV9 +ttQHUD7jszGNtyjNKMmo422QMZzTx38YJ+aR5PfW/arkW3RJPpSn5ClbnH1TSmCd +oFHODRxroV7eu+L2fQMmHtcbXKCTWg7lfvgW +-----END CERTIFICATE----- diff --git a/Tests/_data/encrypt2.key b/Tests/_data/encrypt2.key new file mode 100644 index 0000000..2f58e19 --- /dev/null +++ b/Tests/_data/encrypt2.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAvGX70bC9IIjPKIGN3FKR3wNHD5UdXhgEWpMDuQeAgZ01LRc+ +tTactRMsuI3lTXCGOmU+kXpT03GcUEB4sP4ykSw04umDn8UbZ0o9WfzW8c2Es3iY +Y/sDr4f7KUMaGqrARZrA9mJM4jvT49lOVWoiyzZ2Jgx2gDtFyCEW1b+0Hqnx4zjh +BlCfe6XLpGgEtMwZ9tcmV96BBmlNVNHJbjiSqrsE97FTxxXzQgAmYDRcqVAZicNc +oNlDo/nV9A0n5ygA2Mgx6LF0HUAjf9YRXvRQ4BARtDJV9q/dzu5zxolSmZOWxdla +CkTbeITGmRJNRl6BJiQu5kFRmzTO/Egt6bd5DwIDAQABAoIBACtOojFUmFUXPc+I +4GxKCsAiB768P1D24mFTtCJfaBnjYmroEgEj+afiLYCLFa/UcvaPeW+FmCldz1nf +SB8ff85BRDL5DMm4TJFUzn+WEG7rGFsNGLK66+D4uDKG+0QwBhy58ytv8056BD43 +ILuftznRXh1m9gKKHYNgn9goxiXZ816WYgtNDyYw8kgb9v79VKQRqePW60ZcgquD +THmJWz3eO1Ewurbr4PfrhDtCUcHzz0GRDY0QATCUrAfyl4YbzHrsjnxy4fRq4dXb +01eYZjoD4wchbWOrIMV72urF/KGWYljwOQjwgRgY9q54VrM/Rgga6jj4gWNXLPwn +LN1+/AECgYEA9mnNjNMxAn5sOJkhN430DAv/MSheu0yrNsY3iMjrLJcoVpTxvmos +NxpjWETZFA0T4Yae1VtQjn3LGo1PWJ7j7bCyYdwxuVMHYjCTB107xYg977pMBa37 +6yoN9aZ97p/FeFrOmIoRCO1Xlyu32nhMVBtVCw8TRCks0VuE1gJ84gECgYEAw7pe +7iUaPFzPHzxQc53BcWNEnKSvsNYbiEHLad+kbH76VeVWyf8M6ZSdVE38h7wawYfN +UXPmB2+7ESvvWnXV8zKJCRPMt9ytzH7UxJVCPepvTO8rWPLldUJS3a7sbB9pFFGc +WkPvnKGsnqf6mtj7IO/O4SluR7M9qosg0lxQOw8CgYBzOeqKrb8/QUrt9H1Z8yFp ++LoujIgv4Zw2kt4pMnr2cQDF7ARXXGKsqcRG5Hr2K19emIrxji/PUfeFxQqTkElZ +PsVLiaIe3TqYqco3KVvn9NuxnFYsWb1xrEq20lIVIdU/gIcXQYjRudq5sBHbMWHP ++q/76eLCftacV8V4JdWsAQKBgHYOsjfetUZ3jI8AqF40Z3vnLnl1dGurmYvEc9d2 +iAzRQloRLRpF9xnlBEjXiVyt/02Ahj19NOCDakhfQc5EiTpZ3wJUqQS13TcdwWSZ +ywzhnSTAllrel7z0tlr0qbJF9/HDkBV6KMtHUYGZPLWt7zvcqeJyRQyGdsmphbCc +8d/NAoGBAJ1ahF7h9ezHs60EJ3AxDoA3SHfv9DM5lyz4Kahr7gxBeENkdD6vP+JC +E5LVlen6SgdNg2LDY6rcYm8uHT8Agf234FcTiq4IbiKuRu/QbdPXjN/LrXPFmjgw +hPaPjrm2jSDBdaHlO/wFUq2Oo8hSlWrqHiUCTLg/TfkGegYL+RUc +-----END RSA PRIVATE KEY----- diff --git a/Tests/_data/intermediate.crt b/Tests/_data/intermediate.crt new file mode 100644 index 0000000..cf9c422 --- /dev/null +++ b/Tests/_data/intermediate.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFjCCAf4CAQEwDQYJKoZIhvcNAQEFBQAwTDEXMBUGA1UEAwwOU3ltZm9ueU1p +bWUgQ0ExFDASBgNVBAoMC1N5bWZvbnlNaW1lMQ4wDAYDVQQHDAVQYXJpczELMAkG +A1UEBhMCRlIwHhcNMTkwNDE5MTQyMDEzWhcNMjMwNDE4MTQyMDEzWjBWMSEwHwYD +VQQDDBhTeW1mb255TWltZSBJbnRlcm1lZGlhdGUxFDASBgNVBAoMC1N5bWZvbnlN +aW1lMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDMvRxMxQyecc1bTVZeRCSjBTEmHFZQ2Taqmk5UwO2T +UGsk4nJRnpFHKqSQJgUX8Lj/Y1sjEM+IWrzVAhCPvsAc5x5mU2smylRKzkTCBUkH +UBrbqRBAHVeWu0W58E6D6zo6kweGD7fX+gtMeJY/pcib9tlGPUaVST1TaXYbZD8f +dWD3cN+32FAUyH+TgCtOwYAcwpQ+Npe8X1P/JHIixZz9Eb2WvtiyYqnEDLhKdS9b +zrUZsknkxUguMdd3n3kOO8scd6PTI8k699hOewtDkR7LPDelxhhIazo3kClQV24f +Dd6ktX8L/sGCG7+YTTRpKB47fdEVtiZjLlyZ0K8ih8BZAgMBAAEwDQYJKoZIhvcN +AQEFBQADggEBAIn7oIEeFGCeAUto5PHv3/hHTqLMZZI+VgSxC7zCKBkH59S+ua/s +8HUPRVbBk8qtApz0kL+p4LeUr0mQIQUXSKeyvp6jplMnrgZ1NXck1D9x14oBesiS +q8aVEfwH2DsyJi/0UE4boIeSlk9I0Jh1JSN2jX+zSF0RYYPrTOJKqBfu3QgLgt9s +PbsgOAcHhmWdwDRdFyu/Ok0pieqcHM3TMOV1DPU1aXKtzkCMOHHWfR2bXnIuw1aT +7koX52/3nq9xQ/17ly7iiZAgTWXC9mlnbgO/izWb2WdXHoLkFPrl8IPi3Enf+lo5 +xbpVMU82bgYtgM/Sm2RYV0vUZ9kp50SYy4M= +-----END CERTIFICATE----- diff --git a/Tests/_data/intermediate.key b/Tests/_data/intermediate.key new file mode 100644 index 0000000..b622a6b --- /dev/null +++ b/Tests/_data/intermediate.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAzL0cTMUMnnHNW01WXkQkowUxJhxWUNk2qppOVMDtk1BrJOJy +UZ6RRyqkkCYFF/C4/2NbIxDPiFq81QIQj77AHOceZlNrJspUSs5EwgVJB1Aa26kQ +QB1XlrtFufBOg+s6OpMHhg+31/oLTHiWP6XIm/bZRj1GlUk9U2l2G2Q/H3Vg93Df +t9hQFMh/k4ArTsGAHMKUPjaXvF9T/yRyIsWc/RG9lr7YsmKpxAy4SnUvW861GbJJ +5MVILjHXd595DjvLHHej0yPJOvfYTnsLQ5Eeyzw3pcYYSGs6N5ApUFduHw3epLV/ +C/7Bghu/mE00aSgeO33RFbYmYy5cmdCvIofAWQIDAQABAoIBAQDMIfWYeZOWWsM0 +uExX2rtoquGRLQnGvHwr54QYLu/xRGo/sWPoCyCwg0zmyHGlqAbb4/VXZgh13HqQ +KunWWIr1hl6iCaQ5XdxjZXvasyhYGT9eKhegxWCyUfA4bufp0dwR0MzclslnltAz +I7wyo5n8H0gNJ0U7zXVOuEThFLd3JWg2oJLfnjgWYi1sq0sA3VTKX2L4iwY+vnlN +d/i4Q09jorR38YZzEFjirc6YgOYHmEa8rx5oAZxEViRyAS/yvPTfvxB6HwxYbF4e +EB95VHNft490diDm0RHO8vaw4G8jpP7r8ObXiqwAoKxb8+AHNy765DmbyXp89eFU +SSRNYgQxAoGBAPjTYiq3jU1ykyyhci/y+T1ix+Jhjy/5WKbogKYalEDFbErEChM7 +zMjQNo92Vl4CgN+CpcY/SBVIZozbsT2nJWCN8FaQnZjNK4BlndN30bCp+Mu3sBDl +jZaOdm4Svif9kXPooG7wTcxadvGb+pRNy8zmZCWej1y9/13/FQdh+L1dAoGBANKk +UJ0wJf8F4jR5PC2Db9JlluVHfPP39d1eGGS/FzfbSbmRZImU2eZI1ItWE0whYF06 +WMULqzdRLdcft/SHux8d832ZyHLqc5t4Xip5QE+XEk26S2ZCcjoOd2Ez4NIN5YNm +veac9udX7oiVX8cKn3zGxyrEHLjIB+XW5Yq+KeMtAoGAEQkv9GrCwuWwS+L11XCW +PeywcMBrNEanGi5a+IRjWBfsNSY85lo2yBzxT1szyJX1SthAD1Wv0r01QDmeZfE2 +ruio5tRZ5edOLilG5/6RHb5VaWU3KcD9s6wnUZv45vYGamAn89CCExayhBJA0ryM +0oeHncfAWwIrJL1dLDc594UCgYEAuSahjWlzHJUJXmJqWP89XUzatDKATNpaDPjW +rEejmv9v8GMyYhSq69Z8rPU+BR8ZWxkcSieVmgwLJRrGUXS1MAbdrjtsjEY01CWb +b+4gb1U1S4lDGWGykgGBQbmeFkUMxtGafojeJj+OdhQGmihmRAFds+Op82owNwEL +x0ab/wkCgYEAuCJQiyRzO/X/jq6CdaClDhCiMW6z5JZced6VZS+6s7aA5vmhcl8f +TAZp7BeHqGscPHfpzY6P7lZmqQL2OWURFFjgJYbRWZtXOYGH8ZhBsGA3uAIwyses +2XxUbSYZ5jNvp7r3GwqIsFFth4QRtGEBMdUgCS2mZkRsW6A5NXmeiKA= +-----END RSA PRIVATE KEY----- diff --git a/Tests/_data/sign.crt b/Tests/_data/sign.crt new file mode 100644 index 0000000..3cdb0cd --- /dev/null +++ b/Tests/_data/sign.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOzCCAiMCCQDFHjZEW7DHnjANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T +eW1mb255TWltZSBDQTEUMBIGA1UECgwLU3ltZm9ueU1pbWUxDjAMBgNVBAcMBVBh +cmlzMQswCQYDVQQGEwJGUjAeFw0xOTA1MTIxMjA0MzdaFw0yMzA1MTExMjA0Mzda +MHMxGzAZBgNVBAMMEmZhYmllbkBzeW1mb255LmNvbTEUMBIGA1UECgwLU3ltZm9u +eU1pbWUxDjAMBgNVBAcMBVBhcmlzMQswCQYDVQQGEwJGUjEhMB8GCSqGSIb3DQEJ +ARYSZmFiaWVuQHN5bWZvbnkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAqCDgfKFvJ1NdE0MmSjiVA6Z8ydmZZBsfAE57q9+2bjepW5qwVmL/Igvz +hjjeWFiFIDuLhKkBFZmDR22pkGm5yZRQY8DDXB3Oz0qXr3tVwY4/Iiq+AQhSQfPw +xA11ahRkU14U1CfPc+XdN+Bfv+iwcX8itlz/auHF5BnwFMWcE0A6UC9/70owayia +rVMEWKuYxHrG89t6p3CgKxBG4gF7uxZhy80qVfJWG5ZcCH57xwD/hgQ0We23H89M +G4cpYDX8FZfjzeaVEikOJ9/RK3P6pb5EHtfsO42s2G+j6MnrVTTIA9g326VLW3Vf +3xIrWpGQbwwvm9wiARhEUV+o7QmdXwIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQB+ +mQRLFCkuKfq+pPr9a5p5HfxBbVrNveAOEGRsC83aD4vtWT4X2NoE1MkW2n/AgXtv +AmF/duynnRurKGH8k0Fkw5fMEE+GChwwmJk6UxIV5oO6khk05QAua+5dI2c6rf0z +xanXQksuQDjynnUKNbwyMGAUBcWPlpBeyUKThNWGyRgVuM/7nihI77Rqm2WGHRac +RcCoosNXG0othSWzz0hsxuqsPneO0hGAf3UZI/b+gJk9/SJelUvIRStHBoQRB7YZ +y7kXDwcQZS/IkIGDyWxV1KIpZ0Ban5+0awEG1ShUyepy1dV/24frwu3VOgRN4jv4 +2CGR71B5H0zIRjazNERL +-----END CERTIFICATE----- diff --git a/Tests/_data/sign.key b/Tests/_data/sign.key new file mode 100644 index 0000000..68d1d57 --- /dev/null +++ b/Tests/_data/sign.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAqCDgfKFvJ1NdE0MmSjiVA6Z8ydmZZBsfAE57q9+2bjepW5qw +VmL/IgvzhjjeWFiFIDuLhKkBFZmDR22pkGm5yZRQY8DDXB3Oz0qXr3tVwY4/Iiq+ +AQhSQfPwxA11ahRkU14U1CfPc+XdN+Bfv+iwcX8itlz/auHF5BnwFMWcE0A6UC9/ +70owayiarVMEWKuYxHrG89t6p3CgKxBG4gF7uxZhy80qVfJWG5ZcCH57xwD/hgQ0 +We23H89MG4cpYDX8FZfjzeaVEikOJ9/RK3P6pb5EHtfsO42s2G+j6MnrVTTIA9g3 +26VLW3Vf3xIrWpGQbwwvm9wiARhEUV+o7QmdXwIDAQABAoIBAQCD/gOfdLGqAwVw +SOh3noJGcl9HrKCC+dPVzsfCwIgdcW9xLjlAKMo59X4DIwRUAXLKQlUfGfty9Kke +25YifQ5RljGijsQQvooNLXd2WfKSWVVxQnMWpmzFwHiFwjcqx8WXuaXKhVKVn6GT +63/gTxKul+wtlUckpwlQMZjNBfKpHR56GWTpvvMiEhssGvt601rRj9Dk+f/nWTJo +Qp2Ka2O616/2jF/BuDj7nsK5ePvDotHf4dlRsLLHk8hoqpamByQsepLltvGu4p7U +bAYGOkYJyPtBuLyoYxOBtJrrewUsxo7jEuTfO9j7JHaYqG99QDEWaGlqXGR3481x +emFDVDnBAoGBANaq6mYCkNb84EaR0OzVdj89b5wcECds3UNvGy/h8fx5X9s1Xal2 +EVG976nY4r2wEGKnwCb0qhh94BUmFoxF0iPe3dwlLz9H2ymmWu1v36CMKc92f0UM +3TzeaWrBHdk72PZE8nBTuKkQabboH9LHf2EUrnjNwoY0p9a4uZbLPNi/AoGBAMiA +BKg59MI6GN/9mp+n9D7jdl6ZqhLfYJFGPADRnRPIS89TKywACVNwYCpC2wBRHJ6W +F05BQW2U5ohF2w4n4UODwBadQhP1dg/l40ZO5+BL98Dx8g3bwItCTe25bYtAS0wI +dULj/AR4zdMQCdrh2zzWwPfoNT+6gXfI8V40nsNhAoGBAIvFBw9aVlIUnjZ0lLLP +nck5SCU9xGrXIA3bFrmLhNKdeIMy8QP4Yvh1Ecnl9GQLce+6R4tVvDZsJu2+Oeol +P9ipMI05DNVIBPPOY9+6+sD+4e45uk4MPTR3n+2pRbT+mZpnc+8dI9u4WwyDgMzt +pgtguuTfG+vj9vAAoJ4FQF3jAoGAbmSuK8HdVaOPVqTXodhjzsyGvAd3cPS0wsgc ++YZwKhg6RWjReGR8vgg9qocs9buzOk4BfwDG+YLme1mbBuxGR1ofRVRIsZyQ6Kf2 +vxtq6EBrpTyRvbelCAf1yFI0UluQGcj+Z1oHxJ6PFQrbojyA7bqAfP7JctFJv55P +50Kpt4ECgYEAqjYa3J1YJsK+xTMEG1mAzPZcGG09/Od5Zc7XRY8SkaKwW6eRRu7G +Eq0RuXLW5wxM32sIJyhNqTSchWcntqV/cwvLDmI+JFg3gJ9fNzEdRyHGccBDe7ad +bdR/9n23NVBfaLd6lLszFUQW8efmbzI0HQO1USKRTsFm2TkkcHlafts= +-----END RSA PRIVATE KEY----- diff --git a/Tests/_data/sign2.crt b/Tests/_data/sign2.crt new file mode 100644 index 0000000..c107dfd --- /dev/null +++ b/Tests/_data/sign2.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGTCCAgECAQEwDQYJKoZIhvcNAQEFBQAwVjEhMB8GA1UEAwwYU3ltZm9ueU1p +bWUgSW50ZXJtZWRpYXRlMRQwEgYDVQQKDAtTeW1mb255TWltZTEOMAwGA1UEBwwF +UGFyaXMxCzAJBgNVBAYTAkZSMB4XDTE5MDQxOTE0MjAxNloXDTIzMDQxODE0MjAx +NlowTzEaMBgGA1UEAwwRU3ltZm9ueU1pbWUtVXNlcjIxFDASBgNVBAoMC1N5bWZv +bnlNaW1lMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCuXCpZPt3ikKzhKePg4ra9ynLMyaRZI1PLHJ9G +01XjAJhz1w1yRk7N+AhtEHQK86UwAQLHTOt6XZU62Ifh2yWWkuhDCnO7leQFtJnr +GAzuvXwUfK/Fa+gqmhf0HU5QAnSMmH7w3ViprT2YyoP9aa4G5sD8/DoHBejV4oCE +QFevUuaeKov9rWo81pkREBM8CVkghFIdbbj/gegAmmK2SApkvATx7JCWh3oPtSJ8 +CCuPwLtE9aDfdT7LyuI8x+O8MHVeFB3LvBTOlzPPs43/N8RU7WX1/VTpREIyWC7J +I2bF8V6qLdYknYWm8VMlBlCWj73SuZreUWYesxUFmLrRgLeZAgMBAAEwDQYJKoZI +hvcNAQEFBQADggEBAFQlQzsZfdZ8Z5uZVRM2JG7Ga70cBMd/wS9J/We1ECujgGJD ++smJCONNHmobZswy3EoMaHlUDvUA35gTvEkA+XMXItEfJLPY75j9zRdOZWYI0Y+G +XWt4Bhrh7Dswtci8NUs8TPqJlmLMYJFFEbnxdZr+o2/KIkdVoCjpXM7fa4GLBnD3 +aM59/yclNFCghxGhCYF+nEOoIIet35lxsTC3Pmo/5nDI9fOgjt6yYeiWOM7eHIOJ +G37mWWFODhLnzlA6uRPCjkMzRZnJYiSx7/kJkxqsPJVzIH3vCgHzRnt7JYoKCxqE +nvM0FdQ9+HG4VKggElSdVbKAgt8XjGHeSmVPd+M= +-----END CERTIFICATE----- diff --git a/Tests/_data/sign2.key b/Tests/_data/sign2.key new file mode 100644 index 0000000..ae4ffcb --- /dev/null +++ b/Tests/_data/sign2.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEArlwqWT7d4pCs4Snj4OK2vcpyzMmkWSNTyxyfRtNV4wCYc9cN +ckZOzfgIbRB0CvOlMAECx0zrel2VOtiH4dsllpLoQwpzu5XkBbSZ6xgM7r18FHyv +xWvoKpoX9B1OUAJ0jJh+8N1Yqa09mMqD/WmuBubA/Pw6BwXo1eKAhEBXr1LmniqL +/a1qPNaZERATPAlZIIRSHW24/4HoAJpitkgKZLwE8eyQlod6D7UifAgrj8C7RPWg +33U+y8riPMfjvDB1XhQdy7wUzpczz7ON/zfEVO1l9f1U6URCMlguySNmxfFeqi3W +JJ2FpvFTJQZQlo+90rma3lFmHrMVBZi60YC3mQIDAQABAoIBAGL4/Cz2q5rdBtU1 +Ix5XcuXe0jV+zGSw0fK8h4j7k4gsoV04GHDiif8OqTHHoidJUF4kZMBe4FfwYTIr +EU7aR8bmEyNi/njfx7SZZLl3SHgIZTN354qICxyLpcczD24JRsE8Gup8qsR+CzX8 +1tl1MIzIVYoFXqb36sfmL49iuqNQ2qyrH+OnDKHhBYPrHt8FuhZ1XYyRhtqgjJAD +YMYE6+vA4gwN4Ajk/a9XE05awQTUiLcaXtMxVGB1fejK3xnN/HGOFR8xH5Qi0aSf +vMaJ7rh4fwYjRPQTBVPnm8HA7CVf0dyoCBjK63OJvd+RVj3w4t4/3BeV9VsDVRiT +PFEoJwECgYEA1RZcMkz+Os67e3Ot9NOFReup2PmFhh6PvrqJd0oAlTc/+f6fUI6G +INJNhuCCVg96cw76gfvdN4a3EPgyV708Y16m4EhHpu7jMtGprnbX3Y5H8NjGs62n +ziKw6sCa25xdXFV2c0M0uQVUJAk7sWxpDU3OWkxpmMRPdhbawEdKVyECgYEA0Xk7 +exS2+SRkrveMM2jj6dVZLQ00Tj0WQOovKloUXp9K8ACzXVdgcMeBSQhzQFN1SwAZ +EdEvR9c6dizRHTnfXK+sSMURjMw0e9Fca9vs43oYOW0bIzAzpRJrkh2ZlwDlDuZ0 +ST25nUpCKn7s6eq3XIIs0vNuJNaW2KKIoHqBaXkCgYAHdxkTyg6+ELAQyyS1BxQM +Nw1kRJmg8UEn9XELdNRAZgcfwwPh1pxsWfHNX+AxE6m+ji/IjgJaB6YyOf/Jgx+y +e4ZtJRsdhhD/nsjLC+7UHD/4+B89/D98wUphbw3906SRr4zOzPPz53PjL0+gD6Q+ +ixNHppWsfHQsNvDC+7xnAQKBgHWMi7V5HWjYZGvPXOzomqV45S8j7stM+nT5NfiV +TkL/LwVZz029H9CKFGIQjOR3MSYiau8VrWuqOxNf+QVmmZKgvpSjikKxwW4OQcgB +RYEt3fQz5vurLAAhQx5e3/beOKxQ5MbJDaVXq6O/UGHAJp+SKWdD1fZ0OXheVT+B +H6g5AoGAZBjDmmAca4SMT5nC4Ueh6PlYt0Yr4pMTdkrYTkGxzak6VnRlGg8rmiCP +LSO9vaCoriMSouhVKdBdTjD6lwYxxt4O2HY8D3RgqvU8T7uiI58l2XVdCbs0cBGE +IlNC0CjIiHPWuVhCQk5eLk4WLDvbLq6Oc+8J4BOFupvq3KrGKik= +-----END RSA PRIVATE KEY----- diff --git a/Tests/_data/sign3.crt b/Tests/_data/sign3.crt new file mode 100644 index 0000000..3f907cf --- /dev/null +++ b/Tests/_data/sign3.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFzCCAf8CCQDFHjZEW7DHnDANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T +eW1mb255TWltZSBDQTEUMBIGA1UECgwLU3ltZm9ueU1pbWUxDjAMBgNVBAcMBVBh +cmlzMQswCQYDVQQGEwJGUjAeFw0xOTA0MjcwOTQ2MDRaFw0yMzA0MjYwOTQ2MDRa +ME8xGjAYBgNVBAMMEVN5bWZvbnlNaW1lLVVzZXIzMRQwEgYDVQQKDAtTeW1mb255 +TWltZTEOMAwGA1UEBwwFUGFyaXMxCzAJBgNVBAYTAkZSMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAyywWINcIgcxT8GUXTx/5Xa7rkwoMh3gwNcuOhbRg +08xupcsHAe3f6pPosqm5xweaPcw2xI+yrkXxmXEE0LU4qi7qgtBZt2GqYKcFEtZw +fh0YV4b2NHSO/CFTY25AtwW9wUW+GHq6yYRqrjVZnP9mEokNclPNS4QWSyaNuIav +jCB9xdDXjIc24zM4TeM5LaaFoH4qZGhp3MkjRadnMrrYsp9a3XKrYlQ5lqaXyVlC +/aKeBZy5qckoFj0Lep6LV2xlipX4+d5tcZ2FATkXjJK834WRzFMAVhYxqFyj/RIY +IoEk/AdYeg3b1HK19flNNQbpHPayiSg3l90ecbaN5vqgLQIDAQABMA0GCSqGSIb3 +DQEBBQUAA4IBAQBd+sTqEPXn5MLXMNRsV9HuP4VkX79K7ThA4kXW0fj7Bp+Mpfvg +LLUXRVcyzAKz2RgxyNIKHPr3u6OxHXbtGL5IgdH74uCR4MN+srKpLiGAMNjtvWBr +sGG3pIfpw4sFfVkj5zLFH9MLVSkKFu7Ub2KfOh310AnSnMOJpjy8a0MqY+iOcpj7 +ioOdPHaSQX7DZrECKozOzcfqryYBOwkbwrh1juhDYzy3WtgxZe1FRl3O3FKLtmAc +J4At8HbMDOBH/fMR4o4B1miP6K9QWc66hsAsgY3GWrmiBf67sf1u6kNeNPBGcviW +fQndtjrUUmwkAAQaFYjUnVEcfx55p8TrLhRC +-----END CERTIFICATE----- diff --git a/Tests/_data/sign3.key b/Tests/_data/sign3.key new file mode 100644 index 0000000..b8b51bd --- /dev/null +++ b/Tests/_data/sign3.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,5485FF78699F76CB93A05FF9CE7FB4FD + +iaLWaPXmnAweLnqtXLk/NwQOUNTRIV1yN9gHo0aR4fWy3GDPa08AJz+pYuDULQDY +RCejCBHeSqH4UFbbRHkYHsOdpjEUWhOy07TesajkW3Kl3At1T7aa3cCjhYSpeWkc +4wqzbs5byRd/mzp4G17WjJnohBXBdxCea5WzgoGL4ZeX7nqgxwpnh51VvuOBk+vY +W6QHwpaq+lFa/G7lcoWJxmtQd3/O8apjZB9xT65II7ZeMsIP9NRLitoH7IX+GteU +MdVgTMWVyxhEko5SXlqpZUJJ/C7CReHipI8hkfub0zsQj4FkZ/jJa3X18MQFNQZE +YhhJOhGsv3U65J3PRkxEYeK9fIO8V4e+YeaJb3iItmgh4zfdfw0BieC08xYhuA5f +uEzkxqrk9nWaFHtam5/skmFcE5uH3raMLTHDYkFXxqMRQamddlONuZ4Z+oAdr+VM +zGf4oI7Dsie+PEzvbQNfphrI89TOkH8IhT6HATFKvyLG21lKgBMwFOdJg5B4zcuJ +bh/8cqDrP5byxjDe96fOBWoH2g72f4T9SrOmpgdhEL9AOiLuBnVMQDVLthMY1xxg +SuYxrmtPKn3eaeWHpv8RJSToFF4fl2zJm/HKEAd8ghAJa330/CnNCCQSqUrdE0xp +xjVURJvPZxzMS9UEQhXPyYp66GYs/uLuSTjaQwymFgc/l2UMm3zjHhjyn6S382u7 +ih+AM7y8+pIV2n8CbqoG4XR8UEr9w/ayeDSbFKGZhiqhACW8iZATgVOhmeoH7ON3 +ib7FTNHsyr2eEZ97JeKpbG9E3sFEfHJ51d0PScKmzENGP9LNjMzWb3uot0zP0q0s +kzulVIHnkh48gRICPIKeOL894gAL4PmRViP0x7chXk/xULueVb7T8WmCLYI+Kjaz +X/yGCpPmrBFNAiamrHMSh4qUE+zm7OI2jNOu87tRxcXid8O9wQZDZKaE19HZJ84j +bGghr9pKhIXdn3Z4hSyBqkBnUXNPc1g1er4DryluZ/+wKUvDkZpVmygYvQM1598b +p/xkRxlGE3BZiLX1++wIjMJ2xmwLKGiJOR0BexXJWYz0pD22YUSto7lwmGLm3oxg +MYIFqEgaYFc0MsZ7PRiMcovTUr/c2yHMpyveLHSLMFVmxfGKGZ+JcalCDFhkuZem +DcMQ0bs0DjmJZvC18SYH+iym1JXHnkVSUjsYWuVGGp7zArwlyP3W1MOhHcYDVcEl +TikNqrpgl04ti4qcdtH3TrPufMHq7Y9SM0dY1SUctyaO5yD52ozWriPuS1kTtkaL +GwvJhqvkPyO89bK/We0dIv5KoZPFhEWFwkNMNBIT1GI8XfQZTgi5ZJI3DSr1q022 +zvs5FruPwN2qFsvpmiakl4GhoIs8zwqqCDAO5JXhnSEZeEIB2Hsld7pRDAg3D8Me +T10ZOqM3XRzXPfi5zls81KhkrCh/RHiXEnMseT7aLYxvUHQM+Ktr9ar9Zv42BF+r +c5WFWDs58THPODiKYqPHpUBuIV2moTSsSWGzyvQF5TLw9/rtfrwCiBOiaqzO2odo +h7zLceAmxJfcQ6gIaAy8t5JgAUq5Uwkk0WK2Z3RyJAejdxghQI8XNxkmvu/pM5al +-----END RSA PRIVATE KEY----- From ae1d3338b184ff78cb71be6d94bf12d74be240fb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 29 Jun 2019 11:12:05 +0200 Subject: [PATCH 04/41] removed @experimental annotations --- Address.php | 2 -- BodyRendererInterface.php | 2 -- CharacterStream.php | 2 -- DependencyInjection/AddMimeTypeGuesserPass.php | 2 -- Email.php | 2 -- Encoder/AddressEncoderInterface.php | 2 -- Encoder/Base64ContentEncoder.php | 2 -- Encoder/Base64Encoder.php | 2 -- Encoder/Base64MimeHeaderEncoder.php | 2 -- Encoder/ContentEncoderInterface.php | 2 -- Encoder/EightBitContentEncoder.php | 2 -- Encoder/EncoderInterface.php | 2 -- Encoder/IdnAddressEncoder.php | 2 -- Encoder/MimeHeaderEncoderInterface.php | 2 -- Encoder/QpContentEncoder.php | 2 -- Encoder/QpEncoder.php | 2 -- Encoder/QpMimeHeaderEncoder.php | 2 -- Encoder/Rfc2231Encoder.php | 2 -- Exception/AddressEncoderException.php | 2 -- Exception/ExceptionInterface.php | 2 -- Exception/InvalidArgumentException.php | 2 -- Exception/LogicException.php | 2 -- Exception/RfcComplianceException.php | 2 -- Exception/RuntimeException.php | 2 -- FileBinaryMimeTypeGuesser.php | 2 -- FileinfoMimeTypeGuesser.php | 2 -- Header/AbstractHeader.php | 2 -- Header/DateHeader.php | 2 -- Header/HeaderInterface.php | 2 -- Header/Headers.php | 2 -- Header/IdentificationHeader.php | 2 -- Header/MailboxHeader.php | 2 -- Header/MailboxListHeader.php | 2 -- Header/ParameterizedHeader.php | 2 -- Header/PathHeader.php | 2 -- Header/UnstructuredHeader.php | 2 -- Message.php | 2 -- MessageConverter.php | 2 -- MimeTypeGuesserInterface.php | 2 -- MimeTypes.php | 2 -- MimeTypesInterface.php | 2 -- NamedAddress.php | 2 -- Part/AbstractMultipartPart.php | 2 -- Part/AbstractPart.php | 2 -- Part/DataPart.php | 2 -- Part/MessagePart.php | 2 -- Part/Multipart/AlternativePart.php | 2 -- Part/Multipart/DigestPart.php | 2 -- Part/Multipart/FormDataPart.php | 2 -- Part/Multipart/MixedPart.php | 2 -- Part/Multipart/RelatedPart.php | 2 -- Part/SMimePart.php | 2 -- Part/TextPart.php | 2 -- RawMessage.php | 2 -- 54 files changed, 108 deletions(-) diff --git a/Address.php b/Address.php index 86a8042..2897971 100644 --- a/Address.php +++ b/Address.php @@ -20,8 +20,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class Address { diff --git a/BodyRendererInterface.php b/BodyRendererInterface.php index 8fcc401..d692172 100644 --- a/BodyRendererInterface.php +++ b/BodyRendererInterface.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ interface BodyRendererInterface { diff --git a/CharacterStream.php b/CharacterStream.php index 9b80b2e..3b72ede 100644 --- a/CharacterStream.php +++ b/CharacterStream.php @@ -16,8 +16,6 @@ * @author Xavier De Cock * * @internal - * - * @experimental in 4.3 */ final class CharacterStream { diff --git a/DependencyInjection/AddMimeTypeGuesserPass.php b/DependencyInjection/AddMimeTypeGuesserPass.php index 78450b9..e24beb0 100644 --- a/DependencyInjection/AddMimeTypeGuesserPass.php +++ b/DependencyInjection/AddMimeTypeGuesserPass.php @@ -19,8 +19,6 @@ * Registers custom mime types guessers. * * @author Fabien Potencier - * - * @experimental in 4.3 */ class AddMimeTypeGuesserPass implements CompilerPassInterface { diff --git a/Email.php b/Email.php index 7812372..557f2db 100644 --- a/Email.php +++ b/Email.php @@ -21,8 +21,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class Email extends Message { diff --git a/Encoder/AddressEncoderInterface.php b/Encoder/AddressEncoderInterface.php index 5d6ea3c..de477d8 100644 --- a/Encoder/AddressEncoderInterface.php +++ b/Encoder/AddressEncoderInterface.php @@ -15,8 +15,6 @@ /** * @author Christian Schmidt - * - * @experimental in 4.3 */ interface AddressEncoderInterface { diff --git a/Encoder/Base64ContentEncoder.php b/Encoder/Base64ContentEncoder.php index e9c352e..338490b 100644 --- a/Encoder/Base64ContentEncoder.php +++ b/Encoder/Base64ContentEncoder.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ final class Base64ContentEncoder extends Base64Encoder implements ContentEncoderInterface { diff --git a/Encoder/Base64Encoder.php b/Encoder/Base64Encoder.php index 25dae67..7106478 100644 --- a/Encoder/Base64Encoder.php +++ b/Encoder/Base64Encoder.php @@ -13,8 +13,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ class Base64Encoder implements EncoderInterface { diff --git a/Encoder/Base64MimeHeaderEncoder.php b/Encoder/Base64MimeHeaderEncoder.php index 8baee5b..5c06f6d 100644 --- a/Encoder/Base64MimeHeaderEncoder.php +++ b/Encoder/Base64MimeHeaderEncoder.php @@ -13,8 +13,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ final class Base64MimeHeaderEncoder extends Base64Encoder implements MimeHeaderEncoderInterface { diff --git a/Encoder/ContentEncoderInterface.php b/Encoder/ContentEncoderInterface.php index b44e1a4..a45ad04 100644 --- a/Encoder/ContentEncoderInterface.php +++ b/Encoder/ContentEncoderInterface.php @@ -13,8 +13,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ interface ContentEncoderInterface extends EncoderInterface { diff --git a/Encoder/EightBitContentEncoder.php b/Encoder/EightBitContentEncoder.php index 94b838c..8283120 100644 --- a/Encoder/EightBitContentEncoder.php +++ b/Encoder/EightBitContentEncoder.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ final class EightBitContentEncoder implements ContentEncoderInterface { diff --git a/Encoder/EncoderInterface.php b/Encoder/EncoderInterface.php index 3c2ef19..bbf6d48 100644 --- a/Encoder/EncoderInterface.php +++ b/Encoder/EncoderInterface.php @@ -13,8 +13,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ interface EncoderInterface { diff --git a/Encoder/IdnAddressEncoder.php b/Encoder/IdnAddressEncoder.php index 8936047..1c5e32c 100644 --- a/Encoder/IdnAddressEncoder.php +++ b/Encoder/IdnAddressEncoder.php @@ -25,8 +25,6 @@ * the SMTPUTF8 extension. * * @author Christian Schmidt - * - * @experimental in 4.3 */ final class IdnAddressEncoder implements AddressEncoderInterface { diff --git a/Encoder/MimeHeaderEncoderInterface.php b/Encoder/MimeHeaderEncoderInterface.php index f575665..fff2c78 100644 --- a/Encoder/MimeHeaderEncoderInterface.php +++ b/Encoder/MimeHeaderEncoderInterface.php @@ -13,8 +13,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ interface MimeHeaderEncoderInterface { diff --git a/Encoder/QpContentEncoder.php b/Encoder/QpContentEncoder.php index ef2de46..e0b8605 100644 --- a/Encoder/QpContentEncoder.php +++ b/Encoder/QpContentEncoder.php @@ -13,8 +13,6 @@ /** * @author Lars Strojny - * - * @experimental in 4.3 */ final class QpContentEncoder implements ContentEncoderInterface { diff --git a/Encoder/QpEncoder.php b/Encoder/QpEncoder.php index 4ffbaed..ff9b0cc 100644 --- a/Encoder/QpEncoder.php +++ b/Encoder/QpEncoder.php @@ -15,8 +15,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ class QpEncoder implements EncoderInterface { diff --git a/Encoder/QpMimeHeaderEncoder.php b/Encoder/QpMimeHeaderEncoder.php index 0413959..d1d3837 100644 --- a/Encoder/QpMimeHeaderEncoder.php +++ b/Encoder/QpMimeHeaderEncoder.php @@ -13,8 +13,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ final class QpMimeHeaderEncoder extends QpEncoder implements MimeHeaderEncoderInterface { diff --git a/Encoder/Rfc2231Encoder.php b/Encoder/Rfc2231Encoder.php index 4743a72..aa3e062 100644 --- a/Encoder/Rfc2231Encoder.php +++ b/Encoder/Rfc2231Encoder.php @@ -15,8 +15,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ final class Rfc2231Encoder implements EncoderInterface { diff --git a/Exception/AddressEncoderException.php b/Exception/AddressEncoderException.php index 73ef7f3..51ee2e0 100644 --- a/Exception/AddressEncoderException.php +++ b/Exception/AddressEncoderException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class AddressEncoderException extends RfcComplianceException { diff --git a/Exception/ExceptionInterface.php b/Exception/ExceptionInterface.php index 7dbcdc7..1193390 100644 --- a/Exception/ExceptionInterface.php +++ b/Exception/ExceptionInterface.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ interface ExceptionInterface extends \Throwable { diff --git a/Exception/InvalidArgumentException.php b/Exception/InvalidArgumentException.php index 59d04e2..e89ebae 100644 --- a/Exception/InvalidArgumentException.php +++ b/Exception/InvalidArgumentException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { diff --git a/Exception/LogicException.php b/Exception/LogicException.php index 07cb044..0508ee7 100644 --- a/Exception/LogicException.php +++ b/Exception/LogicException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class LogicException extends \LogicException implements ExceptionInterface { diff --git a/Exception/RfcComplianceException.php b/Exception/RfcComplianceException.php index 5dc4cf5..26e4a50 100644 --- a/Exception/RfcComplianceException.php +++ b/Exception/RfcComplianceException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class RfcComplianceException extends \InvalidArgumentException implements ExceptionInterface { diff --git a/Exception/RuntimeException.php b/Exception/RuntimeException.php index 84b11fb..fb018b0 100644 --- a/Exception/RuntimeException.php +++ b/Exception/RuntimeException.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class RuntimeException extends \RuntimeException implements ExceptionInterface { diff --git a/FileBinaryMimeTypeGuesser.php b/FileBinaryMimeTypeGuesser.php index a25ebe4..d0c8d2b 100644 --- a/FileBinaryMimeTypeGuesser.php +++ b/FileBinaryMimeTypeGuesser.php @@ -18,8 +18,6 @@ * Guesses the MIME type with the binary "file" (only available on *nix). * * @author Bernhard Schussek - * - * @experimental in 4.3 */ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface { diff --git a/FileinfoMimeTypeGuesser.php b/FileinfoMimeTypeGuesser.php index 81c62ee..b91a4ff 100644 --- a/FileinfoMimeTypeGuesser.php +++ b/FileinfoMimeTypeGuesser.php @@ -18,8 +18,6 @@ * Guesses the MIME type using the PECL extension FileInfo. * * @author Bernhard Schussek - * - * @experimental in 4.3 */ class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface { diff --git a/Header/AbstractHeader.php b/Header/AbstractHeader.php index 517ee8d..548c192 100644 --- a/Header/AbstractHeader.php +++ b/Header/AbstractHeader.php @@ -17,8 +17,6 @@ * An abstract base MIME Header. * * @author Chris Corbyn - * - * @experimental in 4.3 */ abstract class AbstractHeader implements HeaderInterface { diff --git a/Header/DateHeader.php b/Header/DateHeader.php index 1e1a979..fdc1464 100644 --- a/Header/DateHeader.php +++ b/Header/DateHeader.php @@ -15,8 +15,6 @@ * A Date MIME Header. * * @author Chris Corbyn - * - * @experimental in 4.3 */ final class DateHeader extends AbstractHeader { diff --git a/Header/HeaderInterface.php b/Header/HeaderInterface.php index b0638bc..4546947 100644 --- a/Header/HeaderInterface.php +++ b/Header/HeaderInterface.php @@ -15,8 +15,6 @@ * A MIME Header. * * @author Chris Corbyn - * - * @experimental in 4.3 */ interface HeaderInterface { diff --git a/Header/Headers.php b/Header/Headers.php index c0e45e0..2797058 100644 --- a/Header/Headers.php +++ b/Header/Headers.php @@ -19,8 +19,6 @@ * A collection of headers. * * @author Fabien Potencier - * - * @experimental in 4.3 */ final class Headers { diff --git a/Header/IdentificationHeader.php b/Header/IdentificationHeader.php index 0695b68..91facf7 100644 --- a/Header/IdentificationHeader.php +++ b/Header/IdentificationHeader.php @@ -18,8 +18,6 @@ * An ID MIME Header for something like Message-ID or Content-ID (one or more addresses). * * @author Chris Corbyn - * - * @experimental in 4.3 */ final class IdentificationHeader extends AbstractHeader { diff --git a/Header/MailboxHeader.php b/Header/MailboxHeader.php index c4f48f3..7419d2e 100644 --- a/Header/MailboxHeader.php +++ b/Header/MailboxHeader.php @@ -19,8 +19,6 @@ * A Mailbox MIME Header for something like Sender (one named address). * * @author Fabien Potencier - * - * @experimental in 4.3 */ final class MailboxHeader extends AbstractHeader { diff --git a/Header/MailboxListHeader.php b/Header/MailboxListHeader.php index 51af2da..e6c8bab 100644 --- a/Header/MailboxListHeader.php +++ b/Header/MailboxListHeader.php @@ -19,8 +19,6 @@ * A Mailbox list MIME Header for something like From, To, Cc, and Bcc (one or more named addresses). * * @author Chris Corbyn - * - * @experimental in 4.3 */ final class MailboxListHeader extends AbstractHeader { diff --git a/Header/ParameterizedHeader.php b/Header/ParameterizedHeader.php index 5813dcf..2eeb079 100644 --- a/Header/ParameterizedHeader.php +++ b/Header/ParameterizedHeader.php @@ -15,8 +15,6 @@ /** * @author Chris Corbyn - * - * @experimental in 4.3 */ final class ParameterizedHeader extends UnstructuredHeader { diff --git a/Header/PathHeader.php b/Header/PathHeader.php index 6d16500..cc450d6 100644 --- a/Header/PathHeader.php +++ b/Header/PathHeader.php @@ -18,8 +18,6 @@ * A Path Header, such a Return-Path (one address). * * @author Chris Corbyn - * - * @experimental in 4.3 */ final class PathHeader extends AbstractHeader { diff --git a/Header/UnstructuredHeader.php b/Header/UnstructuredHeader.php index afb9615..2085ddf 100644 --- a/Header/UnstructuredHeader.php +++ b/Header/UnstructuredHeader.php @@ -15,8 +15,6 @@ * A Simple MIME Header. * * @author Chris Corbyn - * - * @experimental in 4.3 */ class UnstructuredHeader extends AbstractHeader { diff --git a/Message.php b/Message.php index db6e13f..fc8aa85 100644 --- a/Message.php +++ b/Message.php @@ -18,8 +18,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class Message extends RawMessage { diff --git a/MessageConverter.php b/MessageConverter.php index b139f1c..a810cb7 100644 --- a/MessageConverter.php +++ b/MessageConverter.php @@ -20,8 +20,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ final class MessageConverter { diff --git a/MimeTypeGuesserInterface.php b/MimeTypeGuesserInterface.php index b7d27f5..68b0505 100644 --- a/MimeTypeGuesserInterface.php +++ b/MimeTypeGuesserInterface.php @@ -15,8 +15,6 @@ * Guesses the MIME type of a file. * * @author Fabien Potencier - * - * @experimental in 4.3 */ interface MimeTypeGuesserInterface { diff --git a/MimeTypes.php b/MimeTypes.php index eea75fa..b60288c 100644 --- a/MimeTypes.php +++ b/MimeTypes.php @@ -33,8 +33,6 @@ * $guesser->registerGuesser(new FileinfoMimeTypeGuesser('/path/to/magic/file')); * * @author Fabien Potencier - * - * @experimental in 4.3 */ final class MimeTypes implements MimeTypesInterface { diff --git a/MimeTypesInterface.php b/MimeTypesInterface.php index bdf2042..9fbd2cc 100644 --- a/MimeTypesInterface.php +++ b/MimeTypesInterface.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ interface MimeTypesInterface extends MimeTypeGuesserInterface { diff --git a/NamedAddress.php b/NamedAddress.php index 0d58708..c6d674f 100644 --- a/NamedAddress.php +++ b/NamedAddress.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ final class NamedAddress extends Address { diff --git a/Part/AbstractMultipartPart.php b/Part/AbstractMultipartPart.php index 34a94d2..76f5812 100644 --- a/Part/AbstractMultipartPart.php +++ b/Part/AbstractMultipartPart.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ abstract class AbstractMultipartPart extends AbstractPart { diff --git a/Part/AbstractPart.php b/Part/AbstractPart.php index 29eaa1e..c9df105 100644 --- a/Part/AbstractPart.php +++ b/Part/AbstractPart.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ abstract class AbstractPart { diff --git a/Part/DataPart.php b/Part/DataPart.php index 1cfb1e6..e843671 100644 --- a/Part/DataPart.php +++ b/Part/DataPart.php @@ -17,8 +17,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class DataPart extends TextPart { diff --git a/Part/MessagePart.php b/Part/MessagePart.php index 64a5340..1b5c23e 100644 --- a/Part/MessagePart.php +++ b/Part/MessagePart.php @@ -18,8 +18,6 @@ * @final * * @author Fabien Potencier - * - * @experimental in 4.3 */ class MessagePart extends DataPart { diff --git a/Part/Multipart/AlternativePart.php b/Part/Multipart/AlternativePart.php index ad316a4..fd75423 100644 --- a/Part/Multipart/AlternativePart.php +++ b/Part/Multipart/AlternativePart.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ final class AlternativePart extends AbstractMultipartPart { diff --git a/Part/Multipart/DigestPart.php b/Part/Multipart/DigestPart.php index 6199e5b..27537f1 100644 --- a/Part/Multipart/DigestPart.php +++ b/Part/Multipart/DigestPart.php @@ -16,8 +16,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ final class DigestPart extends AbstractMultipartPart { diff --git a/Part/Multipart/FormDataPart.php b/Part/Multipart/FormDataPart.php index 75d69a8..813d09a 100644 --- a/Part/Multipart/FormDataPart.php +++ b/Part/Multipart/FormDataPart.php @@ -20,8 +20,6 @@ * Implements RFC 7578. * * @author Fabien Potencier - * - * @experimental in 4.3 */ final class FormDataPart extends AbstractMultipartPart { diff --git a/Part/Multipart/MixedPart.php b/Part/Multipart/MixedPart.php index eaa869f..c8d7028 100644 --- a/Part/Multipart/MixedPart.php +++ b/Part/Multipart/MixedPart.php @@ -15,8 +15,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ final class MixedPart extends AbstractMultipartPart { diff --git a/Part/Multipart/RelatedPart.php b/Part/Multipart/RelatedPart.php index 2d55630..08fdd5f 100644 --- a/Part/Multipart/RelatedPart.php +++ b/Part/Multipart/RelatedPart.php @@ -16,8 +16,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ final class RelatedPart extends AbstractMultipartPart { diff --git a/Part/SMimePart.php b/Part/SMimePart.php index 59142c3..1dfc1ae 100644 --- a/Part/SMimePart.php +++ b/Part/SMimePart.php @@ -15,8 +15,6 @@ /** * @author Sebastiaan Stok - * - * @experimental in 4.4 */ class SMimePart extends AbstractPart { diff --git a/Part/TextPart.php b/Part/TextPart.php index 6a04185..10aa94f 100644 --- a/Part/TextPart.php +++ b/Part/TextPart.php @@ -20,8 +20,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class TextPart extends AbstractPart { diff --git a/RawMessage.php b/RawMessage.php index 16b090c..35f1729 100644 --- a/RawMessage.php +++ b/RawMessage.php @@ -13,8 +13,6 @@ /** * @author Fabien Potencier - * - * @experimental in 4.3 */ class RawMessage implements \Serializable { From 4b7453c0dd9259cf0bc79ac5ce2fdb77ec30c2d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Mon, 1 Jul 2019 10:51:14 +0200 Subject: [PATCH 05/41] Remove @internal annotations for the serilize methods --- Message.php | 6 ------ RawMessage.php | 6 ------ 2 files changed, 12 deletions(-) diff --git a/Message.php b/Message.php index db6e13f..4d6af1c 100644 --- a/Message.php +++ b/Message.php @@ -130,17 +130,11 @@ private function generateMessageId(string $email): string return bin2hex(random_bytes(16)).strstr($email, '@'); } - /** - * @internal - */ public function __serialize(): array { return [$this->headers, $this->body]; } - /** - * @internal - */ public function __unserialize(array $data): void { [$this->headers, $this->body] = $data; diff --git a/RawMessage.php b/RawMessage.php index 16b090c..40a2795 100644 --- a/RawMessage.php +++ b/RawMessage.php @@ -69,17 +69,11 @@ final public function unserialize($serialized) $this->__unserialize(unserialize($serialized)); } - /** - * @internal - */ public function __serialize(): array { return [$this->message]; } - /** - * @internal - */ public function __unserialize(array $data): void { [$this->message] = $data; From 10c23a3c277830baa6f099d0ab49aa26a9c2e35b Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 2 Jul 2019 09:24:49 +0200 Subject: [PATCH 06/41] [Mime] Updated some PHPDoc contents --- Crypto/SMimeEncrypter.php | 3 ++- Crypto/SMimeSigner.php | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Crypto/SMimeEncrypter.php b/Crypto/SMimeEncrypter.php index 72f59ce..d4c4a42 100644 --- a/Crypto/SMimeEncrypter.php +++ b/Crypto/SMimeEncrypter.php @@ -23,7 +23,8 @@ final class SMimeEncrypter extends SMime private $cipher; /** - * @param string|string[] $certificate Either a lone X.509 certificate, or an array of X.509 certificates + * @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s) + * @param int $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php */ public function __construct($certificate, int $cipher = OPENSSL_CIPHER_AES_256_CBC) { diff --git a/Crypto/SMimeSigner.php b/Crypto/SMimeSigner.php index 71ce5df..ef4375c 100644 --- a/Crypto/SMimeSigner.php +++ b/Crypto/SMimeSigner.php @@ -30,13 +30,11 @@ final class SMimeSigner extends SMime private $privateKeyPassphrase; /** - * @see https://secure.php.net/manual/en/openssl.pkcs7.flags.php - * - * @param string $certificate - * @param string $privateKey A file containing the private key (in PEM format) + * @param string $certificate The path of the file containing the signing certificate (in PEM format) + * @param string $privateKey The path of the file containing the private key (in PEM format) * @param string|null $privateKeyPassphrase A passphrase of the private key (if any) - * @param string $extraCerts A file containing intermediate certificates (in PEM format) needed by the signing certificate - * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() + * @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate + * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php) */ public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, int $signOptions = PKCS7_DETACHED) { From 7b5e7482951ab45b686860784f4d9c29004105c5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 5 Jul 2019 09:03:06 +0200 Subject: [PATCH 07/41] [Mime] add check for openssl when using SMime --- Crypto/SMimeEncrypter.php | 10 +++++++--- Crypto/SMimeSigner.php | 10 +++++++--- Tests/Crypto/SMimeEncryptorTest.php | 3 +++ Tests/Crypto/SMimeSignerTest.php | 3 +++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Crypto/SMimeEncrypter.php b/Crypto/SMimeEncrypter.php index d4c4a42..d6961a6 100644 --- a/Crypto/SMimeEncrypter.php +++ b/Crypto/SMimeEncrypter.php @@ -24,17 +24,21 @@ final class SMimeEncrypter extends SMime /** * @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s) - * @param int $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php + * @param int|null $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php */ - public function __construct($certificate, int $cipher = OPENSSL_CIPHER_AES_256_CBC) + public function __construct($certificate, int $cipher = null) { + if (!\extension_loaded('openssl')) { + throw new \LogicException('PHP extension "openssl" is required to use SMime.'); + } + if (\is_array($certificate)) { $this->certs = array_map([$this, 'normalizeFilePath'], $certificate); } else { $this->certs = $this->normalizeFilePath($certificate); } - $this->cipher = $cipher; + $this->cipher = $cipher ?? OPENSSL_CIPHER_AES_256_CBC; } public function encrypt(Message $message): Message diff --git a/Crypto/SMimeSigner.php b/Crypto/SMimeSigner.php index ef4375c..243aaf1 100644 --- a/Crypto/SMimeSigner.php +++ b/Crypto/SMimeSigner.php @@ -34,10 +34,14 @@ final class SMimeSigner extends SMime * @param string $privateKey The path of the file containing the private key (in PEM format) * @param string|null $privateKeyPassphrase A passphrase of the private key (if any) * @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate - * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php) + * @param int|null $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php) */ - public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, int $signOptions = PKCS7_DETACHED) + public function __construct(string $certificate, string $privateKey, string $privateKeyPassphrase = null, string $extraCerts = null, int $signOptions = null) { + if (!\extension_loaded('openssl')) { + throw new \LogicException('PHP extension "openssl" is required to use SMime.'); + } + $this->signCertificate = $this->normalizeFilePath($certificate); if (null !== $privateKeyPassphrase) { @@ -46,7 +50,7 @@ public function __construct(string $certificate, string $privateKey, ?string $pr $this->signPrivateKey = $this->normalizeFilePath($privateKey); } - $this->signOptions = $signOptions; + $this->signOptions = $signOptions ?? PKCS7_DETACHED; $this->extraCerts = $extraCerts ? realpath($extraCerts) : null; $this->privateKeyPassphrase = $privateKeyPassphrase; } diff --git a/Tests/Crypto/SMimeEncryptorTest.php b/Tests/Crypto/SMimeEncryptorTest.php index a05b1e8..cad87bf 100644 --- a/Tests/Crypto/SMimeEncryptorTest.php +++ b/Tests/Crypto/SMimeEncryptorTest.php @@ -16,6 +16,9 @@ use Symfony\Component\Mime\Email; use Symfony\Component\Mime\Message; +/** + * @requires extension openssl + */ class SMimeEncryptorTest extends SMimeTestCase { public function testEncryptMessage() diff --git a/Tests/Crypto/SMimeSignerTest.php b/Tests/Crypto/SMimeSignerTest.php index ab645b3..0a86c3c 100644 --- a/Tests/Crypto/SMimeSignerTest.php +++ b/Tests/Crypto/SMimeSignerTest.php @@ -18,6 +18,9 @@ use Symfony\Component\Mime\Message; use Symfony\Component\Mime\Part\TextPart; +/** + * @requires extension openssl + */ class SMimeSignerTest extends SMimeTestCase { public function testSignedMessage() From 8749367a9884ca7e67fc4974737ea620d9475c9c Mon Sep 17 00:00:00 2001 From: Teoh Han Hui Date: Wed, 10 Jul 2019 11:45:42 +0200 Subject: [PATCH 08/41] Remove experimental notice from components --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 3288246..4d565c9 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,6 @@ MIME Component The MIME component allows manipulating MIME messages. -**This Component is experimental**. -[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html) -are not covered by Symfony's -[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). - Resources --------- From 5568aaa84d83077025c30c659d8c17dfa66ee9d3 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 18 Jul 2019 19:32:23 +0200 Subject: [PATCH 09/41] [Mailer] Fix phpdoc for variadic methods --- Email.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Email.php b/Email.php index 7812372..3fbebc4 100644 --- a/Email.php +++ b/Email.php @@ -103,7 +103,7 @@ public function getSender(): ?Address } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -113,7 +113,7 @@ public function addFrom(...$addresses) } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -131,7 +131,7 @@ public function getFrom(): array } /** - * @param Address|string $addresses + * @param Address|string ...$addresses * * @return $this */ @@ -141,7 +141,7 @@ public function addReplyTo(...$addresses) } /** - * @param Address|string $addresses + * @param Address|string ...$addresses * * @return $this */ @@ -159,7 +159,7 @@ public function getReplyTo(): array } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -169,7 +169,7 @@ public function addTo(...$addresses) } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -187,7 +187,7 @@ public function getTo(): array } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -197,7 +197,7 @@ public function addCc(...$addresses) } /** - * @param Address|string $addresses + * @param Address|string ...$addresses * * @return $this */ @@ -215,7 +215,7 @@ public function getCc(): array } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -225,7 +225,7 @@ public function addBcc(...$addresses) } /** - * @param Address|string $addresses + * @param Address|string ...$addresses * * @return $this */ From a384217c83b52f9c0fb541e46c1025c65eca3a7a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 18 Jul 2019 19:38:48 +0200 Subject: [PATCH 10/41] [Mime] rename Headers::getAll() to all() --- Header/Headers.php | 6 +++--- Tests/Header/HeadersTest.php | 16 ++++++++-------- Tests/MessageTest.php | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Header/Headers.php b/Header/Headers.php index c0e45e0..57f02b2 100644 --- a/Header/Headers.php +++ b/Header/Headers.php @@ -51,7 +51,7 @@ public function __clone() public function setMaxLineLength(int $lineLength) { $this->lineLength = $lineLength; - foreach ($this->getAll() as $header) { + foreach ($this->all() as $header) { $header->setMaxLineLength($lineLength); } } @@ -177,7 +177,7 @@ public function get(string $name): ?HeaderInterface return array_shift($values); } - public function getAll(string $name = null): iterable + public function all(string $name = null): iterable { if (null === $name) { foreach ($this->headers as $name => $collection) { @@ -220,7 +220,7 @@ public function toString(): string public function toArray(): array { $arr = []; - foreach ($this->getAll() as $header) { + foreach ($this->all() as $header) { if ('' !== $header->getBodyAsString()) { $arr[] = $header->toString(); } diff --git a/Tests/Header/HeadersTest.php b/Tests/Header/HeadersTest.php index 2f4a1dd..3568c9a 100644 --- a/Tests/Header/HeadersTest.php +++ b/Tests/Header/HeadersTest.php @@ -141,7 +141,7 @@ public function testGetReturnsNullIfHeaderNotSet() $this->assertNull($headers->get('Message-ID')); } - public function testGetAllReturnsAllHeadersMatchingName() + public function testAllReturnsAllHeadersMatchingName() { $header0 = new UnstructuredHeader('X-Test', 'some@id'); $header1 = new UnstructuredHeader('X-Test', 'other@id'); @@ -150,10 +150,10 @@ public function testGetAllReturnsAllHeadersMatchingName() $headers->addTextHeader('X-Test', 'some@id'); $headers->addTextHeader('X-Test', 'other@id'); $headers->addTextHeader('X-Test', 'more@id'); - $this->assertEquals([$header0, $header1, $header2], iterator_to_array($headers->getAll('X-Test'))); + $this->assertEquals([$header0, $header1, $header2], iterator_to_array($headers->all('X-Test'))); } - public function testGetAllReturnsAllHeadersIfNoArguments() + public function testAllReturnsAllHeadersIfNoArguments() { $header0 = new IdentificationHeader('Message-ID', 'some@id'); $header1 = new UnstructuredHeader('Subject', 'thing'); @@ -162,13 +162,13 @@ public function testGetAllReturnsAllHeadersIfNoArguments() $headers->addIdHeader('Message-ID', 'some@id'); $headers->addTextHeader('Subject', 'thing'); $headers->addMailboxListHeader('To', [new Address('person@example.org')]); - $this->assertEquals(['message-id' => $header0, 'subject' => $header1, 'to' => $header2], iterator_to_array($headers->getAll())); + $this->assertEquals(['message-id' => $header0, 'subject' => $header1, 'to' => $header2], iterator_to_array($headers->all())); } - public function testGetAllReturnsEmptyArrayIfNoneSet() + public function testAllReturnsEmptyArrayIfNoneSet() { $headers = new Headers(); - $this->assertEquals([], iterator_to_array($headers->getAll('Received'))); + $this->assertEquals([], iterator_to_array($headers->all('Received'))); } public function testRemoveRemovesAllHeadersWithName() @@ -199,12 +199,12 @@ public function testGetIsNotCaseSensitive() $this->assertEquals($header, $headers->get('message-id')); } - public function testGetAllIsNotCaseSensitive() + public function testAllIsNotCaseSensitive() { $header = new IdentificationHeader('Message-ID', 'some@id'); $headers = new Headers(); $headers->addIdHeader('Message-ID', 'some@id'); - $this->assertEquals([$header], iterator_to_array($headers->getAll('message-id'))); + $this->assertEquals([$header], iterator_to_array($headers->all('message-id'))); } public function testRemoveIsNotCaseSensitive() diff --git a/Tests/MessageTest.php b/Tests/MessageTest.php index dbeb0a5..cc806b9 100644 --- a/Tests/MessageTest.php +++ b/Tests/MessageTest.php @@ -68,7 +68,7 @@ public function testGetPreparedHeaders() $message = new Message(); $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']); $h = $message->getPreparedHeaders(); - $this->assertCount(4, iterator_to_array($h->getAll())); + $this->assertCount(4, iterator_to_array($h->all())); $this->assertEquals(new MailboxListHeader('From', [new Address('fabien@symfony.com')]), $h->get('From')); $this->assertEquals(new UnstructuredHeader('MIME-Version', '1.0'), $h->get('mime-version')); $this->assertTrue($h->has('Message-Id')); From 6b7148029b1dd5eda1502064f06d01357b7b2d8b Mon Sep 17 00:00:00 2001 From: Konstantin Myakshin Date: Fri, 19 Jul 2019 01:33:46 +0300 Subject: [PATCH 11/41] [Mime] Add missing changelog entry for BC-break --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..796cfdd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +CHANGELOG +========= + +4.3.3 +----- + + * [BC BREAK] Renamed method `Headers::getAll()` to `Headers::all()`. + +4.3.0 +----- + + * Introduced the component as experimental From a1923d57b2034a3f351523cb7bea51b88000b9a2 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 27 Jul 2019 20:05:40 +0200 Subject: [PATCH 12/41] add parameter type declarations to private methods --- CharacterStream.php | 2 +- Email.php | 4 ++-- Part/Multipart/FormDataPart.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CharacterStream.php b/CharacterStream.php index 3b72ede..faf5359 100644 --- a/CharacterStream.php +++ b/CharacterStream.php @@ -175,7 +175,7 @@ public function write(string $chars): void $this->dataSize = \strlen($this->data) - \strlen($ignored); } - private function getUtf8CharPositions(string $string, int $startOffset, &$ignoredChars): int + private function getUtf8CharPositions(string $string, int $startOffset, string &$ignoredChars): int { $strlen = \strlen($string); $charPos = \count($this->map['p']); diff --git a/Email.php b/Email.php index 9d9b48e..7022554 100644 --- a/Email.php +++ b/Email.php @@ -518,7 +518,7 @@ private function setHeaderBody(string $type, string $name, $body) return $this; } - private function addListAddressHeaderBody($name, array $addresses) + private function addListAddressHeaderBody(string $name, array $addresses) { if (!$to = $this->getHeaders()->get($name)) { return $this->setListAddressHeaderBody($name, $addresses); @@ -528,7 +528,7 @@ private function addListAddressHeaderBody($name, array $addresses) return $this; } - private function setListAddressHeaderBody($name, array $addresses) + private function setListAddressHeaderBody(string $name, array $addresses) { $addresses = Address::createArray($addresses); $headers = $this->getHeaders(); diff --git a/Part/Multipart/FormDataPart.php b/Part/Multipart/FormDataPart.php index 813d09a..88aa1a3 100644 --- a/Part/Multipart/FormDataPart.php +++ b/Part/Multipart/FormDataPart.php @@ -65,7 +65,7 @@ private function prepareFields(array $fields): array return $values; } - private function preparePart($name, $value): TextPart + private function preparePart(string $name, $value): TextPart { if (\is_string($value)) { return $this->configurePart($name, new TextPart($value, 'utf-8', 'plain', '8bit')); From 652456aa060b4fa8c75b5c063a4aea3af1e276d0 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 1 Aug 2019 11:16:35 +0100 Subject: [PATCH 13/41] Ensure signatures for setUp|tearDown|setUpAfterClass|tearDownAfterClass methods in tests are compatible with phpunit 8.2 --- Tests/AbstractMimeTypeGuesserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AbstractMimeTypeGuesserTest.php b/Tests/AbstractMimeTypeGuesserTest.php index f9f5ec5..3ac9382 100644 --- a/Tests/AbstractMimeTypeGuesserTest.php +++ b/Tests/AbstractMimeTypeGuesserTest.php @@ -16,7 +16,7 @@ abstract class AbstractMimeTypeGuesserTest extends TestCase { - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { $path = __DIR__.'/Fixtures/mimetypes/to_delete'; if (file_exists($path)) { From 933ec2abbd7f44892363988fc7cee7cc3f6a40de Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 2 Aug 2019 13:51:32 +0200 Subject: [PATCH 14/41] Merge branch '3.4' into 4.3 * 3.4: Fix tests Fix deprecated phpunit annotation --- Tests/Header/IdentificationHeaderTest.php | 18 ++++++------------ Tests/Header/MailboxHeaderTest.php | 4 +--- Tests/Header/MailboxListHeaderTest.php | 4 +--- Tests/Header/PathHeaderTest.php | 8 ++------ 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/Tests/Header/IdentificationHeaderTest.php b/Tests/Header/IdentificationHeaderTest.php index 7d94d4d..b7f0095 100644 --- a/Tests/Header/IdentificationHeaderTest.php +++ b/Tests/Header/IdentificationHeaderTest.php @@ -99,12 +99,10 @@ public function testIdLeftCanBeDotAtom() $this->assertEquals('', $header->getBodyAsString()); } - /** - * @expectedException \Exception - * @expectedMessageException "a b c" is not valid id-left - */ public function testInvalidIdLeftThrowsException() { + $this->expectException('Exception'); + $this->expectExceptionMessage('Email "a b c@d" does not comply with addr-spec of RFC 2822.'); $header = new IdentificationHeader('References', 'a b c@d'); } @@ -137,21 +135,17 @@ public function testIdRigthIsIdnEncoded() $this->assertEquals('', $header->getBodyAsString()); } - /** - * @expectedException \Exception - * @expectedMessageException "b c d" is not valid id-right - */ public function testInvalidIdRightThrowsException() { + $this->expectException('Exception'); + $this->expectExceptionMessage('Email "a@b c d" does not comply with addr-spec of RFC 2822.'); $header = new IdentificationHeader('References', 'a@b c d'); } - /** - * @expectedException \Exception - * @expectedMessageException "abc" is does not contain @ - */ public function testMissingAtSignThrowsException() { + $this->expectException('Exception'); + $this->expectExceptionMessage('Email "abc" does not comply with addr-spec of RFC 2822.'); /* -- RFC 2822, 3.6.4. msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] */ diff --git a/Tests/Header/MailboxHeaderTest.php b/Tests/Header/MailboxHeaderTest.php index 11a7ac9..72f22ff 100644 --- a/Tests/Header/MailboxHeaderTest.php +++ b/Tests/Header/MailboxHeaderTest.php @@ -59,11 +59,9 @@ public function testgetBodyAsString() $this->assertEquals('Fabien =?'.$header->getCharset().'?Q?P=8Ftencier?= ', $header->getBodyAsString()); } - /** - * @expectedException \Symfony\Component\Mime\Exception\AddressEncoderException - */ public function testUtf8CharsInLocalPartThrows() { + $this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException'); $header = new MailboxHeader('Sender', new Address('fabïen@symfony.com')); $header->getBodyAsString(); } diff --git a/Tests/Header/MailboxListHeaderTest.php b/Tests/Header/MailboxListHeaderTest.php index a2a2805..2eee1cf 100644 --- a/Tests/Header/MailboxListHeaderTest.php +++ b/Tests/Header/MailboxListHeaderTest.php @@ -56,11 +56,9 @@ public function testUtf8CharsInDomainAreIdnEncoded() $this->assertEquals(['Chris Corbyn '], $header->getAddressStrings()); } - /** - * @expectedException \Symfony\Component\Mime\Exception\AddressEncoderException - */ public function testUtf8CharsInLocalPartThrows() { + $this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException'); $header = new MailboxListHeader('From', [new NamedAddress('chrïs@swiftmailer.org', 'Chris Corbyn')]); $header->getAddressStrings(); } diff --git a/Tests/Header/PathHeaderTest.php b/Tests/Header/PathHeaderTest.php index 8f41959..6bc029a 100644 --- a/Tests/Header/PathHeaderTest.php +++ b/Tests/Header/PathHeaderTest.php @@ -23,11 +23,9 @@ public function testSingleAddressCanBeSetAndFetched() $this->assertEquals($address, $header->getAddress()); } - /** - * @expectedException \Exception - */ public function testAddressMustComplyWithRfc2822() { + $this->expectException('Exception'); $header = new PathHeader('Return-Path', new Address('chr is@swiftmailer.org')); } @@ -51,11 +49,9 @@ public function testAddressIsIdnEncoded() $this->assertEquals('', $header->getBodyAsString()); } - /** - * @expectedException \Symfony\Component\Mime\Exception\AddressEncoderException - */ public function testAddressMustBeEncodable() { + $this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException'); $header = new PathHeader('Return-Path', new Address('chrïs@swiftmailer.org')); $header->getBodyAsString(); } From 272409fec9a6c14cb074691612afa3f7d1e82198 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 4 Aug 2019 06:55:52 +0200 Subject: [PATCH 15/41] [Mime] added AbstractPart::asDebugString() --- CHANGELOG.md | 5 +++++ Part/AbstractMultipartPart.php | 14 ++++++++++++++ Part/AbstractPart.php | 5 +++++ Part/DataPart.php | 10 ++++++++++ Part/TextPart.php | 13 +++++++++++++ 5 files changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 796cfdd..0b252bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.4.0 +----- + + * Added `AbstractPart::asDebugString()` + 4.3.3 ----- diff --git a/Part/AbstractMultipartPart.php b/Part/AbstractMultipartPart.php index 76f5812..48b8620 100644 --- a/Part/AbstractMultipartPart.php +++ b/Part/AbstractMultipartPart.php @@ -74,6 +74,20 @@ public function bodyToIterable(): iterable yield '--'.$this->getBoundary()."--\r\n"; } + public function asDebugString(): string + { + $str = parent::asDebugString(); + foreach ($this->getParts() as $part) { + $lines = explode("\n", $part->asDebugString()); + $str .= "\n └ ".array_shift($lines); + foreach ($lines as $line) { + $str .= "\n |".$line; + } + } + + return $str; + } + private function getBoundary(): string { if (null === $this->boundary) { diff --git a/Part/AbstractPart.php b/Part/AbstractPart.php index c9df105..93892d9 100644 --- a/Part/AbstractPart.php +++ b/Part/AbstractPart.php @@ -50,6 +50,11 @@ public function toIterable(): iterable yield from $this->bodyToIterable(); } + public function asDebugString(): string + { + return $this->getMediaType().'/'.$this->getMediaSubtype(); + } + abstract public function bodyToString(): string; abstract public function bodyToIterable(): iterable; diff --git a/Part/DataPart.php b/Part/DataPart.php index e843671..128c53e 100644 --- a/Part/DataPart.php +++ b/Part/DataPart.php @@ -103,6 +103,16 @@ public function getPreparedHeaders(): Headers return $headers; } + public function asDebugString(): string + { + $str = parent::asDebugString(); + if (null !== $this->filename) { + $str .= ' filename: '.$this->filename; + } + + return $str; + } + private function generateContentId(): string { return bin2hex(random_bytes(16)).'@symfony'; diff --git a/Part/TextPart.php b/Part/TextPart.php index 10aa94f..77ab980 100644 --- a/Part/TextPart.php +++ b/Part/TextPart.php @@ -144,6 +144,19 @@ public function getPreparedHeaders(): Headers return $headers; } + public function asDebugString(): string + { + $str = parent::asDebugString(); + if (null !== $this->charset) { + $str .= ' charset: '.$this->charset; + } + if (null !== $this->disposition) { + $str .= ' disposition: '.$this->disposition; + } + + return $str; + } + private function getEncoder(): ContentEncoderInterface { if ('8bit' === $this->encoding) { From 40faa8e96c14270a50e23b854f6c5715c56e44f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kowalewski?= Date: Tue, 30 Jul 2019 16:40:17 +0200 Subject: [PATCH 16/41] Create mailBody with only attachments part present --- Email.php | 12 ++++++++---- Tests/EmailTest.php | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Email.php b/Email.php index 3fbebc4..1bcdc8a 100644 --- a/Email.php +++ b/Email.php @@ -423,12 +423,12 @@ public function getBody(): AbstractPart */ private function generateBody(): AbstractPart { - if (null === $this->text && null === $this->html) { - throw new LogicException('A message must have a text and/or an HTML part.'); + [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); + if (null === $this->text && null === $this->html && !$attachmentParts) { + throw new LogicException('A message must have a text or an HTML part or attachments.'); } $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset); - [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); if (null !== $htmlPart) { if (null !== $part) { $part = new AlternativePart($part, $htmlPart); @@ -442,7 +442,11 @@ private function generateBody(): AbstractPart } if ($attachmentParts) { - $part = new MixedPart($part, ...$attachmentParts); + if ($part) { + $part = new MixedPart($part, ...$attachmentParts); + } else { + $part = new MixedPart(...$attachmentParts); + } } return $part; diff --git a/Tests/EmailTest.php b/Tests/EmailTest.php index 1d45cab..d8a982a 100644 --- a/Tests/EmailTest.php +++ b/Tests/EmailTest.php @@ -284,6 +284,10 @@ public function testGenerateBody() $e->html('html content'); $this->assertEquals(new MixedPart($html, $att), $e->getBody()); + $e = new Email(); + $e->attach($file); + $this->assertEquals(new MixedPart($att), $e->getBody()); + $e = new Email(); $e->html('html content'); $e->text('text content'); From 1d47016bf0f2e4ee684c6f6434214bdab579b8d7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 4 Aug 2019 15:04:10 +0200 Subject: [PATCH 17/41] added PHPUnit constraints and assertions for the Mailer --- CHANGELOG.md | 1 + Test/Constraint/EmailAddressContains.php | 74 +++++++++++++++++++++++ Test/Constraint/EmailAttachmentCount.php | 59 ++++++++++++++++++ Test/Constraint/EmailHasHeader.php | 57 +++++++++++++++++ Test/Constraint/EmailHeaderSame.php | 59 ++++++++++++++++++ Test/Constraint/EmailHtmlBodyContains.php | 56 +++++++++++++++++ Test/Constraint/EmailTextBodyContains.php | 56 +++++++++++++++++ 7 files changed, 362 insertions(+) create mode 100644 Test/Constraint/EmailAddressContains.php create mode 100644 Test/Constraint/EmailAttachmentCount.php create mode 100644 Test/Constraint/EmailHasHeader.php create mode 100644 Test/Constraint/EmailHeaderSame.php create mode 100644 Test/Constraint/EmailHtmlBodyContains.php create mode 100644 Test/Constraint/EmailTextBodyContains.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b252bd..dd412eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.4.0 ----- + * Added PHPUnit constraints * Added `AbstractPart::asDebugString()` 4.3.3 diff --git a/Test/Constraint/EmailAddressContains.php b/Test/Constraint/EmailAddressContains.php new file mode 100644 index 0000000..58ef360 --- /dev/null +++ b/Test/Constraint/EmailAddressContains.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Header\MailboxHeader; +use Symfony\Component\Mime\Header\MailboxListHeader; +use Symfony\Component\Mime\RawMessage; + +final class EmailAddressContains extends Constraint +{ + private $headerName; + private $expectedValue; + + public function __construct(string $headerName, string $expectedValue) + { + $this->headerName = $headerName; + $this->expectedValue = $expectedValue; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('contains address "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message)) { + throw new \LogicException('Unable to test a message address on a RawMessage instance.'); + } + + $header = $message->getHeaders()->get($this->headerName); + if ($header instanceof MailboxHeader) { + return $this->expectedValue === $header->Address()->getAddress(); + } elseif ($header instanceof MailboxListHeader) { + foreach ($header->getAddresses() as $address) { + if ($this->expectedValue === $address->getAddress()) { + return true; + } + } + + return false; + } + + throw new \LogicException(sprintf('Unable to test a message address on a non-address header.')); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($message): string + { + return sprintf('the Email %s (value is %s)', $this->toString(), $message->getHeaders()->get($this->headerName)->getBodyAsString()); + } +} diff --git a/Test/Constraint/EmailAttachmentCount.php b/Test/Constraint/EmailAttachmentCount.php new file mode 100644 index 0000000..b219f28 --- /dev/null +++ b/Test/Constraint/EmailAttachmentCount.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\RawMessage; + +final class EmailAttachmentCount extends Constraint +{ + private $expectedValue; + private $transport; + + public function __construct(int $expectedValue, string $transport = null) + { + $this->expectedValue = $expectedValue; + $this->transport = $transport; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has sent "%d" attachment(s)', $this->expectedValue); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) { + throw new \LogicException('Unable to test a message attachment on a RawMessage or Message instance.'); + } + + return $this->expectedValue === \count($message->getAttachments()); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($message): string + { + return 'the Email '.$this->toString(); + } +} diff --git a/Test/Constraint/EmailHasHeader.php b/Test/Constraint/EmailHasHeader.php new file mode 100644 index 0000000..a29f835 --- /dev/null +++ b/Test/Constraint/EmailHasHeader.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\RawMessage; + +final class EmailHasHeader extends Constraint +{ + private $headerName; + + public function __construct(string $headerName) + { + $this->headerName = $headerName; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s"', $this->headerName); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message)) { + throw new \LogicException('Unable to test a message header on a RawMessage instance.'); + } + + return $message->getHeaders()->has($this->headerName); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($message): string + { + return 'the Email '.$this->toString(); + } +} diff --git a/Test/Constraint/EmailHeaderSame.php b/Test/Constraint/EmailHeaderSame.php new file mode 100644 index 0000000..bc7e330 --- /dev/null +++ b/Test/Constraint/EmailHeaderSame.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\RawMessage; + +final class EmailHeaderSame extends Constraint +{ + private $headerName; + private $expectedValue; + + public function __construct(string $headerName, string $expectedValue) + { + $this->headerName = $headerName; + $this->expectedValue = $expectedValue; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message)) { + throw new \LogicException('Unable to test a message header on a RawMessage instance.'); + } + + return $this->expectedValue === $message->getHeaders()->get($this->headerName)->getBodyAsString(); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($message): string + { + return sprintf('the Email %s (value is %s)', $this->toString(), $message->getHeaders()->get($this->headerName)->getBodyAsString()); + } +} diff --git a/Test/Constraint/EmailHtmlBodyContains.php b/Test/Constraint/EmailHtmlBodyContains.php new file mode 100644 index 0000000..2ad1330 --- /dev/null +++ b/Test/Constraint/EmailHtmlBodyContains.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; + +final class EmailHtmlBodyContains extends Constraint +{ + private $expectedText; + + public function __construct(string $expectedText) + { + $this->expectedText = $expectedText; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('contains "%s"', $this->expectedText); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) { + throw new \LogicException('Unable to test a message HTML body on a RawMessage or Message instance.'); + } + + return false !== mb_strpos($message->getHtmlBody(), $this->expectedText); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($email): string + { + return 'the Email HTML body '.$this->toString(); + } +} diff --git a/Test/Constraint/EmailTextBodyContains.php b/Test/Constraint/EmailTextBodyContains.php new file mode 100644 index 0000000..640b01f --- /dev/null +++ b/Test/Constraint/EmailTextBodyContains.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; + +final class EmailTextBodyContains extends Constraint +{ + private $expectedText; + + public function __construct(string $expectedText) + { + $this->expectedText = $expectedText; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('contains "%s"', $this->expectedText); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function matches($message): bool + { + if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) { + throw new \LogicException('Unable to test a message text body on a RawMessage or Message instance.'); + } + + return false !== mb_strpos($message->getTextBody(), $this->expectedText); + } + + /** + * @param RawMessage $message + * + * {@inheritdoc} + */ + protected function failureDescription($email): string + { + return 'the Email text body '.$this->toString(); + } +} From 524dce463bbf90f7f7b81d9dea62412d841635dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Bla=C5=BEek?= Date: Tue, 6 Aug 2019 10:22:04 +0200 Subject: [PATCH 18/41] Remove deprecated assertContains --- Tests/EmailTest.php | 2 +- Tests/Part/MessagePartTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/EmailTest.php b/Tests/EmailTest.php index d8a982a..764f66b 100644 --- a/Tests/EmailTest.php +++ b/Tests/EmailTest.php @@ -330,7 +330,7 @@ public function testGenerateBody() $this->assertCount(2, $parts = $related[0]->getParts()); $this->assertInstanceOf(AlternativePart::class, $parts[0]); $generatedHtml = $parts[0]->getParts()[1]; - $this->assertContains('cid:'.$parts[1]->getContentId(), $generatedHtml->getBody()); + $this->assertStringContainsString('cid:'.$parts[1]->getContentId(), $generatedHtml->getBody()); $content = 'html content '; $r = fopen('php://memory', 'r+', false); diff --git a/Tests/Part/MessagePartTest.php b/Tests/Part/MessagePartTest.php index 3855e08..21a4eb0 100644 --- a/Tests/Part/MessagePartTest.php +++ b/Tests/Part/MessagePartTest.php @@ -23,9 +23,9 @@ class MessagePartTest extends TestCase public function testConstructor() { $p = new MessagePart((new Email())->from('fabien@symfony.com')->text('content')); - $this->assertContains('content', $p->getBody()); - $this->assertContains('content', $p->bodyToString()); - $this->assertContains('content', implode('', iterator_to_array($p->bodyToIterable()))); + $this->assertStringContainsString('content', $p->getBody()); + $this->assertStringContainsString('content', $p->bodyToString()); + $this->assertStringContainsString('content', implode('', iterator_to_array($p->bodyToIterable()))); $this->assertEquals('message', $p->getMediaType()); $this->assertEquals('rfc822', $p->getMediaSubType()); } From 9a61388a6e1f6f44ad2ae5c9ebbc2da5e3d844f4 Mon Sep 17 00:00:00 2001 From: Ruben Jacobs Date: Tue, 6 Aug 2019 14:04:48 +0200 Subject: [PATCH 19/41] [Mime] fixed wrong mimetype --- MimeTypes.php | 8 ++++---- Resources/bin/update_mime_types.php | 4 ---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/MimeTypes.php b/MimeTypes.php index eea75fa..02e8fe5 100644 --- a/MimeTypes.php +++ b/MimeTypes.php @@ -2433,12 +2433,12 @@ public function guessMimeType(string $path): ?string 'odc' => ['application/vnd.oasis.opendocument.chart'], 'odf' => ['application/vnd.oasis.opendocument.formula'], 'odft' => ['application/vnd.oasis.opendocument.formula-template'], - 'odg' => ['vnd.oasis.opendocument.graphics', 'application/vnd.oasis.opendocument.graphics'], + 'odg' => ['application/vnd.oasis.opendocument.graphics'], 'odi' => ['application/vnd.oasis.opendocument.image'], 'odm' => ['application/vnd.oasis.opendocument.text-master'], - 'odp' => ['vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.presentation'], - 'ods' => ['vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.spreadsheet'], - 'odt' => ['vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.text'], + 'odp' => ['application/vnd.oasis.opendocument.presentation'], + 'ods' => ['application/vnd.oasis.opendocument.spreadsheet'], + 'odt' => ['application/vnd.oasis.opendocument.text'], 'oga' => ['audio/ogg', 'audio/vorbis', 'audio/x-flac+ogg', 'audio/x-ogg', 'audio/x-oggflac', 'audio/x-speex+ogg', 'audio/x-vorbis', 'audio/x-vorbis+ogg'], 'ogg' => ['audio/ogg', 'audio/vorbis', 'audio/x-flac+ogg', 'audio/x-ogg', 'audio/x-oggflac', 'audio/x-speex+ogg', 'audio/x-vorbis', 'audio/x-vorbis+ogg', 'video/ogg', 'video/x-ogg', 'video/x-theora', 'video/x-theora+ogg'], 'ogm' => ['video/x-ogm', 'video/x-ogm+ogg'], diff --git a/Resources/bin/update_mime_types.php b/Resources/bin/update_mime_types.php index 0311d0d..74a9449 100644 --- a/Resources/bin/update_mime_types.php +++ b/Resources/bin/update_mime_types.php @@ -108,10 +108,6 @@ 'mp4' => ['video/mp4'], 'mpeg' => ['video/mpeg'], 'mpg' => ['video/mpeg'], - 'odg' => ['vnd.oasis.opendocument.graphics'], - 'odp' => ['vnd.oasis.opendocument.presentation'], - 'ods' => ['vnd.oasis.opendocument.spreadsheet'], - 'odt' => ['vnd.oasis.opendocument.text'], 'ogg' => ['audio/ogg'], 'pdf' => ['application/pdf'], 'php' => ['application/x-php'], From 452e372f5369ff8516f18f0d2fad2ac5f9db1831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Thu, 8 Aug 2019 21:31:13 +0200 Subject: [PATCH 20/41] Fix deprecation in 4.4 branche --- Tests/Crypto/SMimeTestCase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Crypto/SMimeTestCase.php b/Tests/Crypto/SMimeTestCase.php index f562f18..0bd3c13 100644 --- a/Tests/Crypto/SMimeTestCase.php +++ b/Tests/Crypto/SMimeTestCase.php @@ -55,7 +55,7 @@ protected function iterableToString(iterable $iterable): string protected function assertMessageHeaders(Message $message, RawMessage $originalMessage): void { $messageString = $message->toString(); - self::assertNotContains('Bcc: ', $messageString, '', true); + self::assertStringNotContainsString('Bcc: ', $messageString, '', true); if (!$originalMessage instanceof Message) { return; @@ -67,7 +67,7 @@ protected function assertMessageHeaders(Message $message, RawMessage $originalMe if ($originalMessage->getHeaders()->has('Subject')) { self::assertEquals($originalMessage->getHeaders()->get('Subject'), $message->getPreparedHeaders()->get('Subject')); - self::assertContains('Subject:', $messageString, '', true); + self::assertStringContainsString('Subject:', $messageString, '', true); } } } From 395463b058be12f58d374d744d96517b599e9856 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 14 Aug 2019 14:26:46 +0200 Subject: [PATCH 21/41] Merge branch '3.4' into 4.3 * 3.4: Remove superfluous phpdoc tags --- Header/MailboxHeader.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Header/MailboxHeader.php b/Header/MailboxHeader.php index c4f48f3..c664923 100644 --- a/Header/MailboxHeader.php +++ b/Header/MailboxHeader.php @@ -61,9 +61,6 @@ public function setAddress(Address $address) $this->address = $address; } - /** - * @return Address - */ public function getAddress(): Address { return $this->address; From c95705f7c5b92349f49f4f94c4e23ab2f2fd45d9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 Aug 2019 19:26:39 +0200 Subject: [PATCH 22/41] [4.4] Add return types on internal|final|private methods (bis) --- Header/Headers.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Header/Headers.php b/Header/Headers.php index dc8cb34..1c054e2 100644 --- a/Header/Headers.php +++ b/Header/Headers.php @@ -64,7 +64,7 @@ public function getMaxLineLength(): int * * @return $this */ - public function addMailboxListHeader(string $name, array $addresses) + public function addMailboxListHeader(string $name, array $addresses): self { return $this->add(new MailboxListHeader($name, Address::createArray($addresses))); } @@ -74,7 +74,7 @@ public function addMailboxListHeader(string $name, array $addresses) * * @return $this */ - public function addMailboxHeader(string $name, $address) + public function addMailboxHeader(string $name, $address): self { return $this->add(new MailboxHeader($name, Address::create($address))); } @@ -84,7 +84,7 @@ public function addMailboxHeader(string $name, $address) * * @return $this */ - public function addIdHeader(string $name, $ids) + public function addIdHeader(string $name, $ids): self { return $this->add(new IdentificationHeader($name, $ids)); } @@ -94,7 +94,7 @@ public function addIdHeader(string $name, $ids) * * @return $this */ - public function addPathHeader(string $name, $path) + public function addPathHeader(string $name, $path): self { return $this->add(new PathHeader($name, $path instanceof Address ? $path : new Address($path))); } @@ -102,7 +102,7 @@ public function addPathHeader(string $name, $path) /** * @return $this */ - public function addDateHeader(string $name, \DateTimeInterface $dateTime) + public function addDateHeader(string $name, \DateTimeInterface $dateTime): self { return $this->add(new DateHeader($name, $dateTime)); } @@ -110,7 +110,7 @@ public function addDateHeader(string $name, \DateTimeInterface $dateTime) /** * @return $this */ - public function addTextHeader(string $name, string $value) + public function addTextHeader(string $name, string $value): self { return $this->add(new UnstructuredHeader($name, $value)); } @@ -118,7 +118,7 @@ public function addTextHeader(string $name, string $value) /** * @return $this */ - public function addParameterizedHeader(string $name, string $value, array $params = []) + public function addParameterizedHeader(string $name, string $value, array $params = []): self { return $this->add(new ParameterizedHeader($name, $value, $params)); } @@ -131,7 +131,7 @@ public function has(string $name): bool /** * @return $this */ - public function add(HeaderInterface $header) + public function add(HeaderInterface $header): self { static $map = [ 'date' => DateHeader::class, From fe47dcb7a2e9f4fefa291512d04e18b51c8bb2b1 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 21 Aug 2019 09:00:46 +0200 Subject: [PATCH 23/41] [Mime] Remove NamedAddress --- Address.php | 13 ++++++-- CHANGELOG.md | 1 + Email.php | 28 ++++++++--------- Header/Headers.php | 5 ++- Header/MailboxHeader.php | 3 +- Header/MailboxListHeader.php | 17 +++++------ NamedAddress.php | 42 -------------------------- Tests/AddressTest.php | 11 +++++-- Tests/EmailTest.php | 11 +++---- Tests/Header/MailboxHeaderTest.php | 11 +++---- Tests/Header/MailboxListHeaderTest.php | 23 +++++++------- Tests/MessageTest.php | 5 ++- Tests/NamedAddressTest.php | 27 ----------------- 13 files changed, 67 insertions(+), 130 deletions(-) delete mode 100644 NamedAddress.php delete mode 100644 Tests/NamedAddressTest.php diff --git a/Address.php b/Address.php index 2897971..55fa808 100644 --- a/Address.php +++ b/Address.php @@ -21,14 +21,15 @@ /** * @author Fabien Potencier */ -class Address +final class Address { private static $validator; private static $encoder; private $address; + private $name; - public function __construct(string $address) + public function __construct(string $address, string $name = '') { if (!class_exists(EmailValidator::class)) { throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s"; try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class)); @@ -43,6 +44,7 @@ public function __construct(string $address) } $this->address = $address; + $this->name = $name; } public function getAddress(): string @@ -50,6 +52,11 @@ public function getAddress(): string return $this->address; } + public function getName(): string + { + return $this->name; + } + public function getEncodedAddress(): string { if (null === self::$encoder) { @@ -61,7 +68,7 @@ public function getEncodedAddress(): string public function toString(): string { - return $this->getEncodedAddress(); + return ($n = $this->getName()) ? $n.' <'.$this->getEncodedAddress().'>' : $this->getEncodedAddress(); } /** diff --git a/CHANGELOG.md b/CHANGELOG.md index dd412eb..9f22a40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.4.0 ----- + * [BC BREAK] Removed `NamedAddress` (`Address` now supports a name) * Added PHPUnit constraints * Added `AbstractPart::asDebugString()` diff --git a/Email.php b/Email.php index dace1cf..9c1a1c9 100644 --- a/Email.php +++ b/Email.php @@ -101,7 +101,7 @@ public function getSender(): ?Address } /** - * @param Address|NamedAddress|string ...$addresses + * @param Address|string ...$addresses * * @return $this */ @@ -111,7 +111,7 @@ public function addFrom(...$addresses) } /** - * @param Address|NamedAddress|string ...$addresses + * @param Address|string ...$addresses * * @return $this */ @@ -121,7 +121,7 @@ public function from(...$addresses) } /** - * @return (Address|NamedAddress)[] + * @return Address[] */ public function getFrom(): array { @@ -157,7 +157,7 @@ public function getReplyTo(): array } /** - * @param Address|NamedAddress|string ...$addresses + * @param Address|string ...$addresses * * @return $this */ @@ -167,7 +167,7 @@ public function addTo(...$addresses) } /** - * @param Address|NamedAddress|string ...$addresses + * @param Address|string ...$addresses * * @return $this */ @@ -177,7 +177,7 @@ public function to(...$addresses) } /** - * @return (Address|NamedAddress)[] + * @return Address[] */ public function getTo(): array { @@ -185,7 +185,7 @@ public function getTo(): array } /** - * @param Address|NamedAddress|string ...$addresses + * @param Address|string ...$addresses * * @return $this */ @@ -205,7 +205,7 @@ public function cc(...$addresses) } /** - * @return (Address|NamedAddress)[] + * @return Address[] */ public function getCc(): array { @@ -213,7 +213,7 @@ public function getCc(): array } /** - * @param Address|NamedAddress|string ...$addresses + * @param Address|string ...$addresses * * @return $this */ @@ -233,7 +233,7 @@ public function bcc(...$addresses) } /** - * @return (Address|NamedAddress)[] + * @return Address[] */ public function getBcc(): array { @@ -524,10 +524,10 @@ private function setHeaderBody(string $type, string $name, $body) private function addListAddressHeaderBody(string $name, array $addresses) { - if (!$to = $this->getHeaders()->get($name)) { + if (!$header = $this->getHeaders()->get($name)) { return $this->setListAddressHeaderBody($name, $addresses); } - $to->addAddresses(Address::createArray($addresses)); + $header->addAddresses(Address::createArray($addresses)); return $this; } @@ -536,8 +536,8 @@ private function setListAddressHeaderBody(string $name, array $addresses) { $addresses = Address::createArray($addresses); $headers = $this->getHeaders(); - if ($to = $headers->get($name)) { - $to->setAddresses($addresses); + if ($header = $headers->get($name)) { + $header->setAddresses($addresses); } else { $headers->addMailboxListHeader($name, $addresses); } diff --git a/Header/Headers.php b/Header/Headers.php index 1c054e2..9de506e 100644 --- a/Header/Headers.php +++ b/Header/Headers.php @@ -13,7 +13,6 @@ use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Exception\LogicException; -use Symfony\Component\Mime\NamedAddress; /** * A collection of headers. @@ -60,7 +59,7 @@ public function getMaxLineLength(): int } /** - * @param (NamedAddress|Address|string)[] $addresses + * @param (Address|string)[] $addresses * * @return $this */ @@ -70,7 +69,7 @@ public function addMailboxListHeader(string $name, array $addresses): self } /** - * @param NamedAddress|Address|string $address + * @param Address|string $address * * @return $this */ diff --git a/Header/MailboxHeader.php b/Header/MailboxHeader.php index 40e0b54..25dea2b 100644 --- a/Header/MailboxHeader.php +++ b/Header/MailboxHeader.php @@ -13,7 +13,6 @@ use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Exception\RfcComplianceException; -use Symfony\Component\Mime\NamedAddress; /** * A Mailbox MIME Header for something like Sender (one named address). @@ -67,7 +66,7 @@ public function getAddress(): Address public function getBodyAsString(): string { $str = $this->address->getEncodedAddress(); - if ($this->address instanceof NamedAddress && $name = $this->address->getName()) { + if ($name = $this->address->getName()) { $str = $this->createPhrase($this, $name, $this->getCharset(), true).' <'.$str.'>'; } diff --git a/Header/MailboxListHeader.php b/Header/MailboxListHeader.php index e6c8bab..e58d9d4 100644 --- a/Header/MailboxListHeader.php +++ b/Header/MailboxListHeader.php @@ -13,7 +13,6 @@ use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Exception\RfcComplianceException; -use Symfony\Component\Mime\NamedAddress; /** * A Mailbox list MIME Header for something like From, To, Cc, and Bcc (one or more named addresses). @@ -25,7 +24,7 @@ final class MailboxListHeader extends AbstractHeader private $addresses = []; /** - * @param (NamedAddress|Address)[] $addresses + * @param Address[] $addresses */ public function __construct(string $name, array $addresses) { @@ -35,7 +34,7 @@ public function __construct(string $name, array $addresses) } /** - * @param (NamedAddress|Address)[] $body + * @param Address[] $body * * @throws RfcComplianceException */ @@ -47,7 +46,7 @@ public function setBody($body) /** * @throws RfcComplianceException * - * @return (NamedAddress|Address)[] + * @return Address[] */ public function getBody() { @@ -57,7 +56,7 @@ public function getBody() /** * Sets a list of addresses to be shown in this Header. * - * @param (NamedAddress|Address)[] $addresses + * @param Address[] $addresses * * @throws RfcComplianceException */ @@ -70,7 +69,7 @@ public function setAddresses(array $addresses) /** * Sets a list of addresses to be shown in this Header. * - * @param (NamedAddress|Address)[] $addresses + * @param Address[] $addresses * * @throws RfcComplianceException */ @@ -90,7 +89,7 @@ public function addAddress(Address $address) } /** - * @return (NamedAddress|Address)[] + * @return Address[] */ public function getAddresses(): array { @@ -109,8 +108,8 @@ public function getAddressStrings(): array $strings = []; foreach ($this->addresses as $address) { $str = $address->getEncodedAddress(); - if ($address instanceof NamedAddress && $name = $address->getName()) { - $str = $this->createPhrase($this, $name, $this->getCharset(), empty($strings)).' <'.$str.'>'; + if ($name = $address->getName()) { + $str = $this->createPhrase($this, $name, $this->getCharset(), !$strings).' <'.$str.'>'; } $strings[] = $str; } diff --git a/NamedAddress.php b/NamedAddress.php deleted file mode 100644 index c6d674f..0000000 --- a/NamedAddress.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Mime; - -/** - * @author Fabien Potencier - */ -final class NamedAddress extends Address -{ - private $name; - - public function __construct(string $address, string $name) - { - parent::__construct($address); - - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function getEncodedNamedAddress(): string - { - return ($n = $this->getName()) ? $n.' <'.$this->getEncodedAddress().'>' : $this->getEncodedAddress(); - } - - public function toString(): string - { - return $this->getEncodedNamedAddress(); - } -} diff --git a/Tests/AddressTest.php b/Tests/AddressTest.php index dd7d3ce..0a4911b 100644 --- a/Tests/AddressTest.php +++ b/Tests/AddressTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Address; -use Symfony\Component\Mime\NamedAddress; class AddressTest extends TestCase { @@ -23,6 +22,12 @@ public function testConstructor() $this->assertEquals('fabien@symfonï.com', $a->getAddress()); $this->assertEquals('fabien@xn--symfon-nwa.com', $a->toString()); $this->assertEquals('fabien@xn--symfon-nwa.com', $a->getEncodedAddress()); + + $a = new Address('fabien@symfonï.com', 'Fabien'); + $this->assertEquals('Fabien', $a->getName()); + $this->assertEquals('fabien@symfonï.com', $a->getAddress()); + $this->assertEquals('Fabien ', $a->toString()); + $this->assertEquals('fabien@xn--symfon-nwa.com', $a->getEncodedAddress()); } public function testConstructorWithInvalidAddress() @@ -34,7 +39,7 @@ public function testConstructorWithInvalidAddress() public function testCreate() { $this->assertSame($a = new Address('fabien@symfony.com'), Address::create($a)); - $this->assertSame($b = new NamedAddress('helene@symfony.com', 'Helene'), Address::create($b)); + $this->assertSame($b = new Address('helene@symfony.com', 'Helene'), Address::create($b)); $this->assertEquals($a, Address::create('fabien@symfony.com')); } @@ -47,7 +52,7 @@ public function testCreateWrongArg() public function testCreateArray() { $fabien = new Address('fabien@symfony.com'); - $helene = new NamedAddress('helene@symfony.com', 'Helene'); + $helene = new Address('helene@symfony.com', 'Helene'); $this->assertSame([$fabien, $helene], Address::createArray([$fabien, $helene])); $this->assertEquals([$fabien], Address::createArray(['fabien@symfony.com'])); diff --git a/Tests/EmailTest.php b/Tests/EmailTest.php index 764f66b..59c7e89 100644 --- a/Tests/EmailTest.php +++ b/Tests/EmailTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; -use Symfony\Component\Mime\NamedAddress; use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\Mime\Part\Multipart\AlternativePart; use Symfony\Component\Mime\Part\Multipart\MixedPart; @@ -58,7 +57,7 @@ public function testFrom() { $e = new Email(); $helene = new Address('helene@symfony.com'); - $thomas = new NamedAddress('thomas@symfony.com', 'Thomas'); + $thomas = new Address('thomas@symfony.com', 'Thomas'); $caramel = new Address('caramel@symfony.com'); $this->assertSame($e, $e->from('fabien@symfony.com', $helene, $thomas)); @@ -91,7 +90,7 @@ public function testReplyTo() { $e = new Email(); $helene = new Address('helene@symfony.com'); - $thomas = new NamedAddress('thomas@symfony.com', 'Thomas'); + $thomas = new Address('thomas@symfony.com', 'Thomas'); $caramel = new Address('caramel@symfony.com'); $this->assertSame($e, $e->replyTo('fabien@symfony.com', $helene, $thomas)); @@ -124,7 +123,7 @@ public function testTo() { $e = new Email(); $helene = new Address('helene@symfony.com'); - $thomas = new NamedAddress('thomas@symfony.com', 'Thomas'); + $thomas = new Address('thomas@symfony.com', 'Thomas'); $caramel = new Address('caramel@symfony.com'); $this->assertSame($e, $e->to('fabien@symfony.com', $helene, $thomas)); @@ -157,7 +156,7 @@ public function testCc() { $e = new Email(); $helene = new Address('helene@symfony.com'); - $thomas = new NamedAddress('thomas@symfony.com', 'Thomas'); + $thomas = new Address('thomas@symfony.com', 'Thomas'); $caramel = new Address('caramel@symfony.com'); $this->assertSame($e, $e->cc('fabien@symfony.com', $helene, $thomas)); @@ -190,7 +189,7 @@ public function testBcc() { $e = new Email(); $helene = new Address('helene@symfony.com'); - $thomas = new NamedAddress('thomas@symfony.com', 'Thomas'); + $thomas = new Address('thomas@symfony.com', 'Thomas'); $caramel = new Address('caramel@symfony.com'); $this->assertSame($e, $e->bcc('fabien@symfony.com', $helene, $thomas)); diff --git a/Tests/Header/MailboxHeaderTest.php b/Tests/Header/MailboxHeaderTest.php index 72f22ff..cca27db 100644 --- a/Tests/Header/MailboxHeaderTest.php +++ b/Tests/Header/MailboxHeaderTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Header\MailboxHeader; -use Symfony\Component\Mime\NamedAddress; class MailboxHeaderTest extends TestCase { @@ -44,17 +43,17 @@ public function testgetBodyAsString() $header->setAddress(new Address('fabien@sïmfony.com')); $this->assertEquals('fabien@xn--smfony-iwa.com', $header->getBodyAsString()); - $header = new MailboxHeader('Sender', new NamedAddress('fabien@symfony.com', 'Fabien Potencier')); + $header = new MailboxHeader('Sender', new Address('fabien@symfony.com', 'Fabien Potencier')); $this->assertEquals('Fabien Potencier ', $header->getBodyAsString()); - $header = new MailboxHeader('Sender', new NamedAddress('fabien@symfony.com', 'Fabien Potencier, "from Symfony"')); + $header = new MailboxHeader('Sender', new Address('fabien@symfony.com', 'Fabien Potencier, "from Symfony"')); $this->assertEquals('"Fabien Potencier, \"from Symfony\"" ', $header->getBodyAsString()); - $header = new MailboxHeader('From', new NamedAddress('fabien@symfony.com', 'Fabien Potencier, \\escaped\\')); + $header = new MailboxHeader('From', new Address('fabien@symfony.com', 'Fabien Potencier, \\escaped\\')); $this->assertEquals('"Fabien Potencier, \\\\escaped\\\\" ', $header->getBodyAsString()); $name = 'P'.pack('C', 0x8F).'tencier'; - $header = new MailboxHeader('Sender', new NamedAddress('fabien@symfony.com', 'Fabien '.$name)); + $header = new MailboxHeader('Sender', new Address('fabien@symfony.com', 'Fabien '.$name)); $header->setCharset('iso-8859-1'); $this->assertEquals('Fabien =?'.$header->getCharset().'?Q?P=8Ftencier?= ', $header->getBodyAsString()); } @@ -71,7 +70,7 @@ public function testToString() $header = new MailboxHeader('Sender', new Address('fabien@symfony.com')); $this->assertEquals('Sender: fabien@symfony.com', $header->toString()); - $header = new MailboxHeader('Sender', new NamedAddress('fabien@symfony.com', 'Fabien Potencier')); + $header = new MailboxHeader('Sender', new Address('fabien@symfony.com', 'Fabien Potencier')); $this->assertEquals('Sender: Fabien Potencier ', $header->toString()); } } diff --git a/Tests/Header/MailboxListHeaderTest.php b/Tests/Header/MailboxListHeaderTest.php index 2eee1cf..4cace96 100644 --- a/Tests/Header/MailboxListHeaderTest.php +++ b/Tests/Header/MailboxListHeaderTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Header\MailboxListHeader; -use Symfony\Component\Mime\NamedAddress; class MailboxListHeaderTest extends TestCase { @@ -28,7 +27,7 @@ public function testMailboxIsSetForAddress() public function testMailboxIsRenderedForNameAddress() { - $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn')]); + $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org', 'Chris Corbyn')]); $this->assertEquals(['Chris Corbyn '], $header->getAddressStrings()); } @@ -40,32 +39,32 @@ public function testAddressCanBeReturnedForAddress() public function testQuotesInNameAreQuoted() { - $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn, "DHE"')]); + $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org', 'Chris Corbyn, "DHE"')]); $this->assertEquals(['"Chris Corbyn, \"DHE\"" '], $header->getAddressStrings()); } public function testEscapeCharsInNameAreQuoted() { - $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn, \\escaped\\')]); + $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org', 'Chris Corbyn, \\escaped\\')]); $this->assertEquals(['"Chris Corbyn, \\\\escaped\\\\" '], $header->getAddressStrings()); } public function testUtf8CharsInDomainAreIdnEncoded() { - $header = new MailboxListHeader('From', [new NamedAddress('chris@swïftmailer.org', 'Chris Corbyn')]); + $header = new MailboxListHeader('From', [new Address('chris@swïftmailer.org', 'Chris Corbyn')]); $this->assertEquals(['Chris Corbyn '], $header->getAddressStrings()); } public function testUtf8CharsInLocalPartThrows() { $this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException'); - $header = new MailboxListHeader('From', [new NamedAddress('chrïs@swiftmailer.org', 'Chris Corbyn')]); + $header = new MailboxListHeader('From', [new Address('chrïs@swiftmailer.org', 'Chris Corbyn')]); $header->getAddressStrings(); } public function testGetMailboxesReturnsNameValuePairs() { - $header = new MailboxListHeader('From', $addresses = [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn, DHE')]); + $header = new MailboxListHeader('From', $addresses = [new Address('chris@swiftmailer.org', 'Chris Corbyn, DHE')]); $this->assertEquals($addresses, $header->getAddresses()); } @@ -78,7 +77,7 @@ public function testMultipleAddressesAsMailboxStrings() public function testNameIsEncodedIfNonAscii() { $name = 'C'.pack('C', 0x8F).'rbyn'; - $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris '.$name)]); + $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org', 'Chris '.$name)]); $header->setCharset('iso-8859-1'); $addresses = $header->getAddressStrings(); $this->assertEquals('Chris =?'.$header->getCharset().'?Q?C=8Frbyn?= ', array_shift($addresses)); @@ -92,7 +91,7 @@ public function testEncodingLineLengthCalculations() */ $name = 'C'.pack('C', 0x8F).'rbyn'; - $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris '.$name)]); + $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org', 'Chris '.$name)]); $header->setCharset('iso-8859-1'); $addresses = $header->getAddressStrings(); $this->assertEquals('Chris =?'.$header->getCharset().'?Q?C=8Frbyn?= ', array_shift($addresses)); @@ -100,13 +99,13 @@ public function testEncodingLineLengthCalculations() public function testGetValueReturnsMailboxStringValue() { - $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn')]); + $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org', 'Chris Corbyn')]); $this->assertEquals('Chris Corbyn ', $header->getBodyAsString()); } public function testGetValueReturnsMailboxStringValueForMultipleMailboxes() { - $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn'), new NamedAddress('mark@swiftmailer.org', 'Mark Corbyn')]); + $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org', 'Chris Corbyn'), new Address('mark@swiftmailer.org', 'Mark Corbyn')]); $this->assertEquals('Chris Corbyn , Mark Corbyn ', $header->getBodyAsString()); } @@ -125,7 +124,7 @@ public function testGetBody() public function testToString() { - $header = new MailboxListHeader('From', [new NamedAddress('chris@example.org', 'Chris Corbyn'), new NamedAddress('mark@example.org', 'Mark Corbyn')]); + $header = new MailboxListHeader('From', [new Address('chris@example.org', 'Chris Corbyn'), new Address('mark@example.org', 'Mark Corbyn')]); $this->assertEquals('From: Chris Corbyn , Mark Corbyn ', $header->toString()); } } diff --git a/Tests/MessageTest.php b/Tests/MessageTest.php index cc806b9..bd5d7ca 100644 --- a/Tests/MessageTest.php +++ b/Tests/MessageTest.php @@ -17,7 +17,6 @@ use Symfony\Component\Mime\Header\MailboxListHeader; use Symfony\Component\Mime\Header\UnstructuredHeader; use Symfony\Component\Mime\Message; -use Symfony\Component\Mime\NamedAddress; use Symfony\Component\Mime\Part\TextPart; class MessageTest extends TestCase @@ -94,9 +93,9 @@ public function testGetPreparedHeadersWithNoFrom() public function testGetPreparedHeadersWithNamedFrom() { $message = new Message(); - $message->getHeaders()->addMailboxListHeader('From', [new NamedAddress('fabien@symfony.com', 'Fabien')]); + $message->getHeaders()->addMailboxListHeader('From', [new Address('fabien@symfony.com', 'Fabien')]); $h = $message->getPreparedHeaders(); - $this->assertEquals(new MailboxListHeader('From', [new NamedAddress('fabien@symfony.com', 'Fabien')]), $h->get('From')); + $this->assertEquals(new MailboxListHeader('From', [new Address('fabien@symfony.com', 'Fabien')]), $h->get('From')); $this->assertTrue($h->has('Message-Id')); } diff --git a/Tests/NamedAddressTest.php b/Tests/NamedAddressTest.php deleted file mode 100644 index 7284019..0000000 --- a/Tests/NamedAddressTest.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Mime\Tests; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Mime\NamedAddress; - -class NamedAddressTest extends TestCase -{ - public function testConstructor() - { - $a = new NamedAddress('fabien@symfonï.com', 'Fabien'); - $this->assertEquals('Fabien', $a->getName()); - $this->assertEquals('fabien@symfonï.com', $a->getAddress()); - $this->assertEquals('Fabien ', $a->toString()); - $this->assertEquals('fabien@xn--symfon-nwa.com', $a->getEncodedAddress()); - } -} From a371686b67306d9597161d9c6ee9481812763aa6 Mon Sep 17 00:00:00 2001 From: Emirald Mateli Date: Fri, 16 Aug 2019 21:42:36 +0200 Subject: [PATCH 24/41] [Mime] Trim and remove line breaks from NamedAddress name arg --- Address.php | 6 +++--- NamedAddress.php | 2 +- Tests/NamedAddressTest.php | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Address.php b/Address.php index 86a8042..be1ca76 100644 --- a/Address.php +++ b/Address.php @@ -40,11 +40,11 @@ public function __construct(string $address) self::$validator = new EmailValidator(); } - if (!self::$validator->isValid($address, new RFCValidation())) { + $this->address = trim($address); + + if (!self::$validator->isValid($this->address, new RFCValidation())) { throw new RfcComplianceException(sprintf('Email "%s" does not comply with addr-spec of RFC 2822.', $address)); } - - $this->address = $address; } public function getAddress(): string diff --git a/NamedAddress.php b/NamedAddress.php index 0d58708..b13fd73 100644 --- a/NamedAddress.php +++ b/NamedAddress.php @@ -24,7 +24,7 @@ public function __construct(string $address, string $name) { parent::__construct($address); - $this->name = $name; + $this->name = trim(str_replace(["\n", "\r"], '', $name)); } public function getName(): string diff --git a/Tests/NamedAddressTest.php b/Tests/NamedAddressTest.php index 7284019..b793cbb 100644 --- a/Tests/NamedAddressTest.php +++ b/Tests/NamedAddressTest.php @@ -24,4 +24,19 @@ public function testConstructor() $this->assertEquals('Fabien ', $a->toString()); $this->assertEquals('fabien@xn--symfon-nwa.com', $a->getEncodedAddress()); } + + public function nameEmptyDataProvider(): array + { + return [[''], [' '], [" \r\n "]]; + } + + /** + * @dataProvider nameEmptyDataProvider + */ + public function testNameEmpty(string $name) + { + $mail = 'mail@example.org'; + + $this->assertSame($mail, (new NamedAddress($mail, $name))->getEncodedNamedAddress()); + } } From b993c035bde277eb5ec5d22e8a26c1a0b6afef11 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Aug 2019 15:30:43 +0200 Subject: [PATCH 25/41] Add return types to tests and final|internal|private methods --- Header/DateHeader.php | 5 +---- Header/IdentificationHeader.php | 5 +---- Header/MailboxHeader.php | 4 +--- Header/MailboxListHeader.php | 2 +- Header/PathHeader.php | 5 +---- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/Header/DateHeader.php b/Header/DateHeader.php index fdc1464..a7385d4 100644 --- a/Header/DateHeader.php +++ b/Header/DateHeader.php @@ -35,10 +35,7 @@ public function setBody($body) $this->setDateTime($body); } - /** - * @return \DateTimeImmutable - */ - public function getBody() + public function getBody(): \DateTimeImmutable { return $this->getDateTime(); } diff --git a/Header/IdentificationHeader.php b/Header/IdentificationHeader.php index 91facf7..8a94574 100644 --- a/Header/IdentificationHeader.php +++ b/Header/IdentificationHeader.php @@ -44,10 +44,7 @@ public function setBody($body) $this->setId($body); } - /** - * @return array - */ - public function getBody() + public function getBody(): array { return $this->getIds(); } diff --git a/Header/MailboxHeader.php b/Header/MailboxHeader.php index 25dea2b..b58c825 100644 --- a/Header/MailboxHeader.php +++ b/Header/MailboxHeader.php @@ -42,10 +42,8 @@ public function setBody($body) /** * @throws RfcComplianceException - * - * @return Address */ - public function getBody() + public function getBody(): Address { return $this->getAddress(); } diff --git a/Header/MailboxListHeader.php b/Header/MailboxListHeader.php index e58d9d4..1d00fdb 100644 --- a/Header/MailboxListHeader.php +++ b/Header/MailboxListHeader.php @@ -48,7 +48,7 @@ public function setBody($body) * * @return Address[] */ - public function getBody() + public function getBody(): array { return $this->getAddresses(); } diff --git a/Header/PathHeader.php b/Header/PathHeader.php index cc450d6..5101ad0 100644 --- a/Header/PathHeader.php +++ b/Header/PathHeader.php @@ -40,10 +40,7 @@ public function setBody($body) $this->setAddress($body); } - /** - * @return Address - */ - public function getBody() + public function getBody(): Address { return $this->getAddress(); } From 987a05df1c6ac259b34008b932551353f4f408df Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 22 Aug 2019 10:16:11 +0200 Subject: [PATCH 26/41] fix deps=low --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f1d2f09..0697e56 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "symfony/polyfill-mbstring": "^1.0" }, "require-dev": { - "egulias/email-validator": "^2.0", + "egulias/email-validator": "^2.1.10", "symfony/dependency-injection": "~3.4|^4.1" }, "autoload": { From 3462eb4696969a652b363525682cdb00833e1ee7 Mon Sep 17 00:00:00 2001 From: Giso Stallenberg Date: Thu, 22 Aug 2019 11:02:01 +0200 Subject: [PATCH 27/41] Add Address::fromString This will allow to create an Address from a string such as 'Name ' --- Address.php | 20 ++++++++++++ CHANGELOG.md | 1 + Tests/AddressTest.php | 76 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/Address.php b/Address.php index ffa5635..d744aa1 100644 --- a/Address.php +++ b/Address.php @@ -23,6 +23,15 @@ */ final class Address { + /** + * A regex that matches a structure like 'Name '. + * It matches anything between the first < and last > as email address. + * This allows to use a single string to construct an Address, which can be convenient to use in + * config, and allows to have more readable config. + * This does not try to cover all edge cases for address. + */ + private const FROM_STRING_PATTERN = '~(?[^<]*)<(?.*)>[^>]*~'; + private static $validator; private static $encoder; @@ -100,4 +109,15 @@ public static function createArray(array $addresses): array return $addrs; } + + public static function fromString(string $string): self + { + if (false === strpos($string, '<')) { + return new self($string, ''); + } + if (!preg_match(self::FROM_STRING_PATTERN, $string, $matches)) { + throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $string, static::class)); + } + return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"')); + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f22a40..6148360 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * [BC BREAK] Removed `NamedAddress` (`Address` now supports a name) * Added PHPUnit constraints * Added `AbstractPart::asDebugString()` + * Added `Address::fromString()` 4.3.3 ----- diff --git a/Tests/AddressTest.php b/Tests/AddressTest.php index 2c6245f..50d5780 100644 --- a/Tests/AddressTest.php +++ b/Tests/AddressTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Exception\InvalidArgumentException; class AddressTest extends TestCase { @@ -77,4 +78,79 @@ public function nameEmptyDataProvider(): array { return [[''], [' '], [" \r\n "]]; } + + /** + * @dataProvider fromStringProvider + */ + public function testFromString($string, $displayName, $addrSpec) + { + $address = Address::fromString($string); + $this->assertEquals($displayName, $address->getName()); + $this->assertEquals($addrSpec, $address->getAddress()); + $fromToStringAddress = Address::fromString($address->toString()); + $this->assertEquals($displayName, $fromToStringAddress->getName()); + $this->assertEquals($addrSpec, $fromToStringAddress->getAddress()); + } + + public function testFromStringFailure() + { + $this->expectException(InvalidArgumentException::class); + Address::fromString('Jane Doe ', + '', + 'example@example.com', + ], + [ + 'Jane Doe ', + 'Jane Doe', + 'example@example.com', + ], + [ + 'Jane Doe', + 'Jane Doe', + 'example@example.com', + ], + [ + '\'Jane Doe\' ', + 'Jane Doe', + 'example@example.com', + ], + [ + '"Jane Doe" ', + 'Jane Doe', + 'example@example.com', + ], + [ + 'Jane Doe <"ex', + 'Jane Doe', + '"exle"@example.com>', + 'Jane Doe', + '"exle"@example.com', + ], + [ + 'Jane Doe > <"exle"@example.com>', + 'Jane Doe >', + '"exle"@example.com', + ], + [ + 'Jane Doe discarded', + 'Jane Doe', + 'example@example.com', + ], + ]; + } } From c2722905ebbce0d0f2d8567de1cac99eb753e0cb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 22 Aug 2019 11:15:28 +0200 Subject: [PATCH 28/41] fixed CSC --- Address.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Address.php b/Address.php index d744aa1..b0dcbd0 100644 --- a/Address.php +++ b/Address.php @@ -115,9 +115,11 @@ public static function fromString(string $string): self if (false === strpos($string, '<')) { return new self($string, ''); } + if (!preg_match(self::FROM_STRING_PATTERN, $string, $matches)) { throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $string, static::class)); } + return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"')); } } From 650af90e1d5287d056ae1eb25b5df601f3669771 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 22 Aug 2019 15:01:48 +0200 Subject: [PATCH 29/41] Add return types to internal & magic methods when possible --- RawMessage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RawMessage.php b/RawMessage.php index 17501ec..8a01049 100644 --- a/RawMessage.php +++ b/RawMessage.php @@ -54,7 +54,7 @@ public function toIterable(): iterable /** * @internal */ - final public function serialize() + final public function serialize(): string { return serialize($this->__serialize()); } From e76994998d8fcbb3fcdb5013494d27154ecab978 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 23 Aug 2019 10:36:56 +0200 Subject: [PATCH 30/41] Add missing return annotations on magic methods --- Part/DataPart.php | 3 +++ Part/TextPart.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Part/DataPart.php b/Part/DataPart.php index 128c53e..423185f 100644 --- a/Part/DataPart.php +++ b/Part/DataPart.php @@ -125,6 +125,9 @@ public function __destruct() } } + /** + * @return array + */ public function __sleep() { // converts the body to a string diff --git a/Part/TextPart.php b/Part/TextPart.php index 77ab980..a41d91d 100644 --- a/Part/TextPart.php +++ b/Part/TextPart.php @@ -179,6 +179,9 @@ private function chooseEncoding(): string return 'quoted-printable'; } + /** + * @return array + */ public function __sleep() { // convert resources to strings for serialization From f988fe9f62c8f577c61a7b7da6b32edf38fe9e00 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 5 Sep 2019 14:01:23 +0200 Subject: [PATCH 31/41] [Mailer] Check email validity before opening an SMTP connection --- Email.php | 14 +++++++++++--- Message.php | 9 +++++++++ RawMessage.php | 9 +++++++++ Tests/EmailTest.php | 27 +++++++++++++-------------- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/Email.php b/Email.php index 9c1a1c9..7ecea47 100644 --- a/Email.php +++ b/Email.php @@ -399,6 +399,15 @@ public function getBody(): AbstractPart return $this->generateBody(); } + public function ensureValidity() + { + if (null === $this->text && null === $this->html && !$this->attachments) { + throw new LogicException('A message must have a text or an HTML part or attachments.'); + } + + parent::ensureValidity(); + } + /** * Generates an AbstractPart based on the raw body of a message. * @@ -421,10 +430,9 @@ public function getBody(): AbstractPart */ private function generateBody(): AbstractPart { + $this->ensureValidity(); + [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); - if (null === $this->text && null === $this->html && !$attachmentParts) { - throw new LogicException('A message must have a text or an HTML part or attachments.'); - } $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset); if (null !== $htmlPart) { diff --git a/Message.php b/Message.php index d141cb5..d927177 100644 --- a/Message.php +++ b/Message.php @@ -123,6 +123,15 @@ public function toIterable(): iterable yield from $body->toIterable(); } + public function ensureValidity() + { + if (!$this->headers->has('From')) { + throw new LogicException('An email must have a "From" header.'); + } + + parent::ensureValidity(); + } + private function generateMessageId(string $email): string { return bin2hex(random_bytes(16)).strstr($email, '@'); diff --git a/RawMessage.php b/RawMessage.php index 8a01049..79a27e9 100644 --- a/RawMessage.php +++ b/RawMessage.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Mime; +use Symfony\Component\Mime\Exception\LogicException; + /** * @author Fabien Potencier */ @@ -51,6 +53,13 @@ public function toIterable(): iterable $this->message = $message; } + /** + * @throws LogicException if the message is not valid + */ + public function ensureValidity() + { + } + /** * @internal */ diff --git a/Tests/EmailTest.php b/Tests/EmailTest.php index 59c7e89..eb792c2 100644 --- a/Tests/EmailTest.php +++ b/Tests/EmailTest.php @@ -251,62 +251,62 @@ public function testGenerateBody() $att = new DataPart($file = fopen(__DIR__.'/Fixtures/mimetypes/test', 'r')); $img = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r'), 'test.gif'); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->text('text content'); $this->assertEquals($text, $e->getBody()); $this->assertEquals('text content', $e->getTextBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->html('html content'); $this->assertEquals($html, $e->getBody()); $this->assertEquals('html content', $e->getHtmlBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->html('html content'); $e->text('text content'); $this->assertEquals(new AlternativePart($text, $html), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->html('html content', 'iso-8859-1'); $e->text('text content', 'iso-8859-1'); $this->assertEquals('iso-8859-1', $e->getTextCharset()); $this->assertEquals('iso-8859-1', $e->getHtmlCharset()); $this->assertEquals(new AlternativePart(new TextPart('text content', 'iso-8859-1'), new TextPart('html content', 'iso-8859-1', 'html')), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->attach($file); $e->text('text content'); $this->assertEquals(new MixedPart($text, $att), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->attach($file); $e->html('html content'); $this->assertEquals(new MixedPart($html, $att), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->attach($file); $this->assertEquals(new MixedPart($att), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->html('html content'); $e->text('text content'); $e->attach($file); $this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->html('html content'); $e->text('text content'); $e->attach($file); $e->attach($image, 'test.gif'); $this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att, $img), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->text('text content'); $e->attach($file); $e->attach($image, 'test.gif'); $this->assertEquals(new MixedPart($text, $att, $img), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->html($content = 'html content '); $e->text('text content'); $e->attach($file); @@ -314,13 +314,12 @@ public function testGenerateBody() $fullhtml = new TextPart($content, 'utf-8', 'html'); $this->assertEquals(new MixedPart(new AlternativePart($text, $fullhtml), $att, $img), $e->getBody()); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->html($content = 'html content '); $e->text('text content'); $e->attach($file); $e->attach($image, 'test.gif'); $fullhtml = new TextPart($content, 'utf-8', 'html'); - $inlinedimg = (new DataPart($image, 'test.gif'))->asInline(); $body = $e->getBody(); $this->assertInstanceOf(MixedPart::class, $body); $this->assertCount(2, $related = $body->getParts()); @@ -336,7 +335,7 @@ public function testGenerateBody() fwrite($r, $content); rewind($r); - $e = new Email(); + $e = (new Email())->from('me@example.com'); $e->html($r); // embedding the same image twice results in one image only in the email $e->embed($image, 'test.gif'); From ed8d050fb4f1605d22ddbfbcb146a7fa26006b89 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 8 Sep 2019 09:09:18 +0200 Subject: [PATCH 32/41] fixed typo --- Tests/Part/Multipart/FormDataPartTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Part/Multipart/FormDataPartTest.php b/Tests/Part/Multipart/FormDataPartTest.php index 127fce0..71a03e6 100644 --- a/Tests/Part/Multipart/FormDataPartTest.php +++ b/Tests/Part/Multipart/FormDataPartTest.php @@ -26,7 +26,7 @@ public function testConstructor() $b = new TextPart('content'); $c = DataPart::fromPath($file = __DIR__.'/../../Fixtures/mimetypes/test.gif'); $f = new FormDataPart([ - 'foo' => $content = 'very very long content that will not be cut even if the length i way more than 76 characters, ok?', + 'foo' => $content = 'very very long content that will not be cut even if the length is way more than 76 characters, ok?', 'bar' => clone $b, 'baz' => clone $c, ]); From 82e1f6ba180dc9984b5565634b1a4a441c6cdec1 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Thu, 5 Sep 2019 17:26:55 +0200 Subject: [PATCH 33/41] Adding .gitattributes to remove Tests directory from "dist" --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..aa02dc6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore From 32f71570547b91879fdbd9cf50317d556ae86916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Terje=20Br=C3=A5ten?= Date: Tue, 17 Sep 2019 22:18:26 +0100 Subject: [PATCH 34/41] Make legacy "wrong" RFC2047 encoding apply only to one header --- Header/ParameterizedHeader.php | 2 +- Tests/Header/ParameterizedHeaderTest.php | 29 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Header/ParameterizedHeader.php b/Header/ParameterizedHeader.php index 5813dcf..811a969 100644 --- a/Header/ParameterizedHeader.php +++ b/Header/ParameterizedHeader.php @@ -38,7 +38,7 @@ public function __construct(string $name, string $value, array $parameters = []) $this->setParameter($k, $v); } - if ('content-disposition' === strtolower($name)) { + if ('content-type' !== strtolower($name)) { $this->encoder = new Rfc2231Encoder(); } } diff --git a/Tests/Header/ParameterizedHeaderTest.php b/Tests/Header/ParameterizedHeaderTest.php index aa82658..e41d038 100644 --- a/Tests/Header/ParameterizedHeaderTest.php +++ b/Tests/Header/ParameterizedHeaderTest.php @@ -205,16 +205,25 @@ public function testValueAndParamCanBeEncodedIfNonAscii() $header = new ParameterizedHeader('X-Foo', $value); $header->setCharset('iso-8859-1'); $header->setParameters(['says' => $value]); - $this->assertEquals('X-Foo: =?'.$header->getCharset().'?Q?fo=8Fbar?=; says="=?'.$header->getCharset().'?Q?fo=8Fbar?="', $header->toString()); + $this->assertEquals('X-Foo: =?'.$header->getCharset().'?Q?fo=8Fbar?=; says*='.$header->getCharset()."''fo%8Fbar", $header->toString()); } - public function testParamsAreEncodedWithEncodedWordsIfNoParamEncoderSet() + public function testParamsAreEncodedIfNonAscii() { $value = 'fo'.pack('C', 0x8F).'bar'; $header = new ParameterizedHeader('X-Foo', 'bar'); $header->setCharset('iso-8859-1'); $header->setParameters(['says' => $value]); - $this->assertEquals('X-Foo: bar; says="=?'.$header->getCharset().'?Q?fo=8Fbar?="', $header->toString()); + $this->assertEquals('X-Foo: bar; says*='.$header->getCharset()."''fo%8Fbar", $header->toString()); + } + + public function testParamsAreEncodedWithLegacyEncodingEnabled() + { + $value = 'fo'.pack('C', 0x8F).'bar'; + $header = new ParameterizedHeader('Content-Type', 'bar'); + $header->setCharset('iso-8859-1'); + $header->setParameters(['says' => $value]); + $this->assertEquals('Content-Type: bar; says="=?'.$header->getCharset().'?Q?fo=8Fbar?="', $header->toString()); } public function testLanguageInformationAppearsInEncodedWords() @@ -234,6 +243,18 @@ public function testLanguageInformationAppearsInEncodedWords() tag. For example: From: =?US-ASCII*EN?Q?Keith_Moore?= + + -- RFC 2047, 5. Use of encoded-words in message headers + ... + + An 'encoded-word' MUST NOT be used in parameter of a MIME + Content-Type or Content-Disposition field, or in any structured + field body except within a 'comment' or 'phrase'. + + -- RFC 2047, Appendix - changes since RFC 1522 + ... + + clarify that encoded-words are allowed in '*text' fields in both + RFC822 headers and MIME body part headers, but NOT as parameter + values. */ $value = 'fo'.pack('C', 0x8F).'bar'; @@ -241,7 +262,7 @@ public function testLanguageInformationAppearsInEncodedWords() $header->setCharset('iso-8859-1'); $header->setLanguage('en'); $header->setParameters(['says' => $value]); - $this->assertEquals('X-Foo: =?'.$header->getCharset().'*en?Q?fo=8Fbar?=; says="=?'.$header->getCharset().'*en?Q?fo=8Fbar?="', $header->toString()); + $this->assertEquals('X-Foo: =?'.$header->getCharset().'*en?Q?fo=8Fbar?=; says*='.$header->getCharset()."'en'fo%8Fbar", $header->toString()); } public function testSetBody() From c7238f7732e267f302422d75a2f118c9aeb28f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ba=CC=81lint=20Szekeres?= Date: Tue, 8 Oct 2019 14:15:27 +0200 Subject: [PATCH 35/41] added image/svg MIME support --- MimeTypes.php | 3 ++- Tests/MimeTypesTest.php | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/MimeTypes.php b/MimeTypes.php index 6b5ad49..268658d 100644 --- a/MimeTypes.php +++ b/MimeTypes.php @@ -1251,6 +1251,7 @@ public function guessMimeType(string $path): ?string 'image/psd' => ['psd'], 'image/rle' => ['rle'], 'image/sgi' => ['sgi'], + 'image/svg' => ['svg'], 'image/svg+xml' => ['svg', 'svgz'], 'image/svg+xml-compressed' => ['svgz'], 'image/tiff' => ['tiff', 'tif'], @@ -2808,7 +2809,7 @@ public function guessMimeType(string $path): ?string 'sv4crc' => ['application/x-sv4crc'], 'svc' => ['application/vnd.dvb.service'], 'svd' => ['application/vnd.svd'], - 'svg' => ['image/svg+xml'], + 'svg' => ['image/svg+xml', 'image/svg'], 'svgz' => ['image/svg+xml', 'image/svg+xml-compressed'], 'svh' => ['text/x-svhdr'], 'swa' => ['application/x-director'], diff --git a/Tests/MimeTypesTest.php b/Tests/MimeTypesTest.php index c5ff262..a736dbe 100644 --- a/Tests/MimeTypesTest.php +++ b/Tests/MimeTypesTest.php @@ -47,6 +47,8 @@ public function testGetExtensions() $mt = new MimeTypes(); $this->assertSame(['mbox'], $mt->getExtensions('application/mbox')); $this->assertSame(['ai', 'eps', 'ps'], $mt->getExtensions('application/postscript')); + $this->assertContains('svg', $mt->getExtensions('image/svg+xml')); + $this->assertContains('svg', $mt->getExtensions('image/svg')); $this->assertSame([], $mt->getExtensions('application/whatever-symfony')); } @@ -56,6 +58,8 @@ public function testGetMimeTypes() $this->assertSame(['application/mbox'], $mt->getMimeTypes('mbox')); $this->assertContains('application/postscript', $mt->getMimeTypes('ai')); $this->assertContains('application/postscript', $mt->getMimeTypes('ps')); + $this->assertContains('image/svg+xml', $mt->getMimeTypes('svg')); + $this->assertContains('image/svg', $mt->getMimeTypes('svg')); $this->assertSame([], $mt->getMimeTypes('symfony')); } } From a5f860133f0850730059519843597a9bccb64ead Mon Sep 17 00:00:00 2001 From: Reedy Date: Sat, 12 Oct 2019 01:27:05 +0100 Subject: [PATCH 36/41] Add .gitignore to .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index aa02dc6..ebb9287 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore +/.gitignore export-ignore From 592a01c2569acaebc0cd64355047d8af755b2b95 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 12 Oct 2019 09:08:57 +0200 Subject: [PATCH 37/41] Add Message-Id to SentMessage when sending an email --- Message.php | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Message.php b/Message.php index d927177..5b4e67f 100644 --- a/Message.php +++ b/Message.php @@ -32,9 +32,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null) public function __clone() { - if (null !== $this->headers) { - $this->headers = clone $this->headers; - } + $this->headers = clone $this->headers; if (null !== $this->body) { $this->body = clone $this->body; @@ -86,16 +84,12 @@ public function getPreparedHeaders(): Headers } // determine the "real" sender - $senders = $headers->get('From')->getAddresses(); - $sender = $senders[0]; - if ($headers->has('Sender')) { - $sender = $headers->get('Sender')->getAddress(); - } elseif (\count($senders) > 1) { - $headers->addMailboxHeader('Sender', $sender); + if (!$headers->has('Sender') && \count($froms = $headers->get('From')->getAddresses()) > 1) { + $headers->addMailboxHeader('Sender', $froms[0]); } if (!$headers->has('Message-ID')) { - $headers->addIdHeader('Message-ID', $this->generateMessageId($sender->getAddress())); + $headers->addIdHeader('Message-ID', $this->generateMessageId()); } // remove the Bcc field which should NOT be part of the sent message @@ -132,9 +126,17 @@ public function ensureValidity() parent::ensureValidity(); } - private function generateMessageId(string $email): string + public function generateMessageId(): string { - return bin2hex(random_bytes(16)).strstr($email, '@'); + if ($this->headers->has('Sender')) { + $sender = $this->headers->get('Sender')->getAddress(); + } elseif ($this->headers->has('From')) { + $sender = $this->headers->get('From')->getAddresses()[0]; + } else { + throw new LogicException('An email must have a "From" or a "Sender" header to compute a Messsage ID.'); + } + + return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@'); } public function __serialize(): array From a6b152c922ee0d392fca4110bd5a954f5e324db7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 12 Oct 2019 11:11:50 +0200 Subject: [PATCH 38/41] Merge branch '3.4' into 4.3 * 3.4: Add plus character `+` to legal mime subtype [Dotenv] search variable values in ENV first then env file [VarDumper] fix resetting the "bold" state in CliDumper SCA: added missing break in a loop --- FileBinaryMimeTypeGuesser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FileBinaryMimeTypeGuesser.php b/FileBinaryMimeTypeGuesser.php index a25ebe4..e00ce65 100644 --- a/FileBinaryMimeTypeGuesser.php +++ b/FileBinaryMimeTypeGuesser.php @@ -85,7 +85,7 @@ public function guessMimeType(string $path): ?string $type = trim(ob_get_clean()); - if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) { + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) { // it's not a type, but an error message return null; } From 51d5b0e72f7871f7538411adb11a32d5252454e2 Mon Sep 17 00:00:00 2001 From: Jorge Vahldick Date: Fri, 18 Oct 2019 15:20:21 +0100 Subject: [PATCH 39/41] Changing the multipart form-data behavior to use the form name as an array, which makes it recognizable as an array by PHP on the $_POST globals once it is coming from the HttpClient component --- Part/Multipart/FormDataPart.php | 17 ++++++++++---- Tests/Part/Multipart/FormDataPartTest.php | 28 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Part/Multipart/FormDataPart.php b/Part/Multipart/FormDataPart.php index 88aa1a3..6838620 100644 --- a/Part/Multipart/FormDataPart.php +++ b/Part/Multipart/FormDataPart.php @@ -56,11 +56,20 @@ public function getParts(): array private function prepareFields(array $fields): array { $values = []; - array_walk_recursive($fields, function ($item, $key) use (&$values) { - if (!\is_array($item)) { - $values[] = $this->preparePart($key, $item); + + $prepare = function ($item, $key, $root = null) use (&$values, &$prepare) { + $fieldName = $root ? sprintf('%s[%s]', $root, $key) : $key; + + if (\is_array($item)) { + array_walk($item, $prepare, $fieldName); + + return; } - }); + + $values[] = $this->preparePart($fieldName, $item); + }; + + array_walk($fields, $prepare); return $values; } diff --git a/Tests/Part/Multipart/FormDataPartTest.php b/Tests/Part/Multipart/FormDataPartTest.php index 71a03e6..a74ecea 100644 --- a/Tests/Part/Multipart/FormDataPartTest.php +++ b/Tests/Part/Multipart/FormDataPartTest.php @@ -47,6 +47,34 @@ public function testConstructor() $this->assertEquals([$t, $b, $c], $f->getParts()); } + public function testNestedArrayParts() + { + $p1 = new TextPart('content', 'utf-8', 'plain', '8bit'); + $f = new FormDataPart([ + 'foo' => clone $p1, + 'bar' => [ + 'baz' => [ + clone $p1, + 'qux' => clone $p1, + ], + ], + ]); + + $this->assertEquals('multipart', $f->getMediaType()); + $this->assertEquals('form-data', $f->getMediaSubtype()); + + $p1->setName('foo'); + $p1->setDisposition('form-data'); + + $p2 = clone $p1; + $p2->setName('bar[baz][0]'); + + $p3 = clone $p1; + $p3->setName('bar[baz][qux]'); + + $this->assertEquals([$p1, $p2, $p3], $f->getParts()); + } + public function testToString() { $p = DataPart::fromPath($file = __DIR__.'/../../Fixtures/mimetypes/test.gif'); From 3c0e197529da6e59b217615ba8ee7604df88b551 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Tue, 29 Oct 2019 19:32:45 +0100 Subject: [PATCH 40/41] [4.3] Remove unused local variables --- CharacterStream.php | 1 - Tests/EmailTest.php | 4 +--- Tests/Header/HeadersTest.php | 4 ---- Tests/Header/IdentificationHeaderTest.php | 6 +++--- Tests/Header/PathHeaderTest.php | 2 +- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/CharacterStream.php b/CharacterStream.php index 9b80b2e..045b477 100644 --- a/CharacterStream.php +++ b/CharacterStream.php @@ -116,7 +116,6 @@ public function read(int $length): ?string if ($this->currentPos >= $this->charCount) { return null; } - $ret = null; $length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length; if ($this->fixedWidth > 0) { $len = $length * $this->fixedWidth; diff --git a/Tests/EmailTest.php b/Tests/EmailTest.php index 764f66b..1822da1 100644 --- a/Tests/EmailTest.php +++ b/Tests/EmailTest.php @@ -320,8 +320,6 @@ public function testGenerateBody() $e->text('text content'); $e->attach($file); $e->attach($image, 'test.gif'); - $fullhtml = new TextPart($content, 'utf-8', 'html'); - $inlinedimg = (new DataPart($image, 'test.gif'))->asInline(); $body = $e->getBody(); $this->assertInstanceOf(MixedPart::class, $body); $this->assertCount(2, $related = $body->getParts()); @@ -378,7 +376,7 @@ public function testSerialize() $e->from('fabien@symfony.com'); $e->text($r); $e->html($r); - $contents = file_get_contents($name = __DIR__.'/Fixtures/mimetypes/test', 'r'); + $name = __DIR__.'/Fixtures/mimetypes/test'; $file = fopen($name, 'r'); $e->attach($file, 'test'); $expected = clone $e; diff --git a/Tests/Header/HeadersTest.php b/Tests/Header/HeadersTest.php index 3568c9a..e2eb75a 100644 --- a/Tests/Header/HeadersTest.php +++ b/Tests/Header/HeadersTest.php @@ -173,8 +173,6 @@ public function testAllReturnsEmptyArrayIfNoneSet() public function testRemoveRemovesAllHeadersWithName() { - $header0 = new UnstructuredHeader('X-Test', 'some@id'); - $header1 = new UnstructuredHeader('X-Test', 'other@id'); $headers = new Headers(); $headers->addIdHeader('X-Test', 'some@id'); $headers->addIdHeader('X-Test', 'other@id'); @@ -185,7 +183,6 @@ public function testRemoveRemovesAllHeadersWithName() public function testHasIsNotCaseSensitive() { - $header = new IdentificationHeader('Message-ID', 'some@id'); $headers = new Headers(); $headers->addIdHeader('Message-ID', 'some@id'); $this->assertTrue($headers->has('message-id')); @@ -209,7 +206,6 @@ public function testAllIsNotCaseSensitive() public function testRemoveIsNotCaseSensitive() { - $header = new IdentificationHeader('Message-ID', 'some@id'); $headers = new Headers(); $headers->addIdHeader('Message-ID', 'some@id'); $headers->remove('message-id'); diff --git a/Tests/Header/IdentificationHeaderTest.php b/Tests/Header/IdentificationHeaderTest.php index b7f0095..7d274ab 100644 --- a/Tests/Header/IdentificationHeaderTest.php +++ b/Tests/Header/IdentificationHeaderTest.php @@ -103,7 +103,7 @@ public function testInvalidIdLeftThrowsException() { $this->expectException('Exception'); $this->expectExceptionMessage('Email "a b c@d" does not comply with addr-spec of RFC 2822.'); - $header = new IdentificationHeader('References', 'a b c@d'); + new IdentificationHeader('References', 'a b c@d'); } public function testIdRightCanBeDotAtom() @@ -139,7 +139,7 @@ public function testInvalidIdRightThrowsException() { $this->expectException('Exception'); $this->expectExceptionMessage('Email "a@b c d" does not comply with addr-spec of RFC 2822.'); - $header = new IdentificationHeader('References', 'a@b c d'); + new IdentificationHeader('References', 'a@b c d'); } public function testMissingAtSignThrowsException() @@ -149,7 +149,7 @@ public function testMissingAtSignThrowsException() /* -- RFC 2822, 3.6.4. msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] */ - $header = new IdentificationHeader('References', 'abc'); + new IdentificationHeader('References', 'abc'); } public function testSetBody() diff --git a/Tests/Header/PathHeaderTest.php b/Tests/Header/PathHeaderTest.php index 6bc029a..a8386f8 100644 --- a/Tests/Header/PathHeaderTest.php +++ b/Tests/Header/PathHeaderTest.php @@ -26,7 +26,7 @@ public function testSingleAddressCanBeSetAndFetched() public function testAddressMustComplyWithRfc2822() { $this->expectException('Exception'); - $header = new PathHeader('Return-Path', new Address('chr is@swiftmailer.org')); + new PathHeader('Return-Path', new Address('chr is@swiftmailer.org')); } public function testValueIsAngleAddrWithValidAddress() From 22aecf6b11638ef378fab25d6c5a2da8a31a1448 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 19 Apr 2019 14:48:43 +0200 Subject: [PATCH 41/41] [Mime] fix guessing mime-types of files with leading dash --- FileBinaryMimeTypeGuesser.php | 4 ++-- Tests/AbstractMimeTypeGuesserTest.php | 15 +++++++++++++++ Tests/Fixtures/mimetypes/-test | Bin 0 -> 35 bytes 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 Tests/Fixtures/mimetypes/-test diff --git a/FileBinaryMimeTypeGuesser.php b/FileBinaryMimeTypeGuesser.php index e00ce65..59e55f7 100644 --- a/FileBinaryMimeTypeGuesser.php +++ b/FileBinaryMimeTypeGuesser.php @@ -33,7 +33,7 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface * * @param string $cmd The command to run to get the MIME type of a file */ - public function __construct(string $cmd = 'file -b --mime %s 2>/dev/null') + public function __construct(string $cmd = 'file -b --mime -- %s 2>/dev/null') { $this->cmd = $cmd; } @@ -76,7 +76,7 @@ public function guessMimeType(string $path): ?string ob_start(); // need to use --mime instead of -i. see #6641 - passthru(sprintf($this->cmd, escapeshellarg($path)), $return); + passthru(sprintf($this->cmd, escapeshellarg((0 === strpos($path, '-') ? './' : '').$path)), $return); if ($return > 0) { ob_end_clean(); diff --git a/Tests/AbstractMimeTypeGuesserTest.php b/Tests/AbstractMimeTypeGuesserTest.php index 3ac9382..70e419c 100644 --- a/Tests/AbstractMimeTypeGuesserTest.php +++ b/Tests/AbstractMimeTypeGuesserTest.php @@ -27,6 +27,21 @@ public static function tearDownAfterClass(): void abstract protected function getGuesser(): MimeTypeGuesserInterface; + public function testGuessWithLeadingDash() + { + if (!$this->getGuesser()->isGuesserSupported()) { + $this->markTestSkipped('Guesser is not supported'); + } + + $cwd = getcwd(); + chdir(__DIR__.'/Fixtures/mimetypes'); + try { + $this->assertEquals('image/gif', $this->getGuesser()->guessMimeType('-test')); + } finally { + chdir($cwd); + } + } + public function testGuessImageWithoutExtension() { if (!$this->getGuesser()->isGuesserSupported()) { diff --git a/Tests/Fixtures/mimetypes/-test b/Tests/Fixtures/mimetypes/-test new file mode 100644 index 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96 GIT binary patch literal 35 jcmZ?wbh9u|WMp7uXkcLY4+c66KmZb9U}AD%WUvMRyAlZ1 literal 0 HcmV?d00001 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy